├── README.rdoc ├── jquery-1.3.2.min.js ├── jquery.parsley.js ├── json2.js ├── qunit.css ├── qunit.js └── tests.html /README.rdoc: -------------------------------------------------------------------------------- 1 | This is a port of the core ideas of Parsley from C to Javascript and jQuery. Parsley is a domain-specific language for extracting content from HTML. It adds two idioms to jQuery. 2 | 3 | The first addition is the extract() method. This transforms a jQuery object acting as a node 4 | list into a StringNodeList list of strings. 5 | 6 | For example, let's perform some extractions on the following HTML: 7 | 8 | HomeGoogle 9 | 10 | Here's some console output from simple use cases: 11 | 12 | js> jQuery("a").extract() 13 | #=> , ]> 14 | js> jQuery("a").extract().simple() 15 | #=> ["Home", "Google"] 16 | 17 | You can also pass regexen, attributes, or arbitrary functions to extract(). 18 | 19 | js> jQuery("a").extract("@href").simple() 20 | #=> ["/", "http://google.com"] 21 | js> jQuery("a").extract(/[A-Z]/).simple() 22 | #=> ["H", "G"] 23 | js> jQuery("a").extract(function(node){ return "hi"; }).simple() 24 | #=> ["hi", "hi"] 25 | 26 | The second idiom is auto-grouping. Individual extractions can be grouped into a larger data structure (called a parselet), which will parse the page in an intelligent way. Here's the naive ungrouped way to use extract: 27 | 28 | js> var parselet = { 29 | links: [{ 30 | text: $("a").extract().simple(), 31 | href: $("a").extract("@href").simple() 32 | }] 33 | }; 34 | #=> { links: [{ text: ["Home", "Google"], href: ["/", "http://google.com"] }] } 35 | 36 | Now, let's add grouping by calling pQuery.extractAndGroup() to transform the data structure into something more convenient. extractAndGroup() will automatically call extract() and simple() as necessary, so this time we'll omit them. 37 | 38 | js> pQuery.extractAndGroup({ 39 | links: [{ 40 | text: $("a") 41 | href: $("a").extract("@href") 42 | }] 43 | }); 44 | #=> { links: [{ text: "Home", href: "/"}, {text: "Google", href: "http://google.com"}]} 45 | 46 | Now the links array has two objects, each representing one link. This is a much better representation of the data. 47 | 48 | The eventual goal of this project is to create a crawler that takes parselets inner {links: ...} object as input, and from that generates a json or csv representaion of an entire website. 49 | 50 | Here's an example parselet that gets a list of stories from http://news.ycombinator.com. 51 | 52 | { 53 | articles: [{ 54 | title: $(".title a"), 55 | link: $(".title a").extract("@href"), 56 | comment_count: $(".subtext a:nth-child(3)").extract(/0-9+/), 57 | comment_link: $(".subtext a:nth-child(3)").extract("@href"), 58 | points: $(".subtext span").extract(/0-9+/) 59 | }], 60 | } -------------------------------------------------------------------------------- /jquery-1.3.2.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery JavaScript Library v1.3.2 3 | * http://jquery.com/ 4 | * 5 | * Copyright (c) 2009 John Resig 6 | * Dual licensed under the MIT and GPL licenses. 7 | * http://docs.jquery.com/License 8 | * 9 | * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009) 10 | * Revision: 6246 11 | */ 12 | (function(){var l=this,g,y=l.jQuery,p=l.$,o=l.jQuery=l.$=function(E,F){return new o.fn.init(E,F)},D=/^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,f=/^.[^:#\[\.,]*$/;o.fn=o.prototype={init:function(E,H){E=E||document;if(E.nodeType){this[0]=E;this.length=1;this.context=E;return this}if(typeof E==="string"){var G=D.exec(E);if(G&&(G[1]||!H)){if(G[1]){E=o.clean([G[1]],H)}else{var I=document.getElementById(G[3]);if(I&&I.id!=G[3]){return o().find(E)}var F=o(I||[]);F.context=document;F.selector=E;return F}}else{return o(H).find(E)}}else{if(o.isFunction(E)){return o(document).ready(E)}}if(E.selector&&E.context){this.selector=E.selector;this.context=E.context}return this.setArray(o.isArray(E)?E:o.makeArray(E))},selector:"",jquery:"1.3.2",size:function(){return this.length},get:function(E){return E===g?Array.prototype.slice.call(this):this[E]},pushStack:function(F,H,E){var G=o(F);G.prevObject=this;G.context=this.context;if(H==="find"){G.selector=this.selector+(this.selector?" ":"")+E}else{if(H){G.selector=this.selector+"."+H+"("+E+")"}}return G},setArray:function(E){this.length=0;Array.prototype.push.apply(this,E);return this},each:function(F,E){return o.each(this,F,E)},index:function(E){return o.inArray(E&&E.jquery?E[0]:E,this)},attr:function(F,H,G){var E=F;if(typeof F==="string"){if(H===g){return this[0]&&o[G||"attr"](this[0],F)}else{E={};E[F]=H}}return this.each(function(I){for(F in E){o.attr(G?this.style:this,F,o.prop(this,E[F],G,I,F))}})},css:function(E,F){if((E=="width"||E=="height")&&parseFloat(F)<0){F=g}return this.attr(E,F,"curCSS")},text:function(F){if(typeof F!=="object"&&F!=null){return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(F))}var E="";o.each(F||this,function(){o.each(this.childNodes,function(){if(this.nodeType!=8){E+=this.nodeType!=1?this.nodeValue:o.fn.text([this])}})});return E},wrapAll:function(E){if(this[0]){var F=o(E,this[0].ownerDocument).clone();if(this[0].parentNode){F.insertBefore(this[0])}F.map(function(){var G=this;while(G.firstChild){G=G.firstChild}return G}).append(this)}return this},wrapInner:function(E){return this.each(function(){o(this).contents().wrapAll(E)})},wrap:function(E){return this.each(function(){o(this).wrapAll(E)})},append:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.appendChild(E)}})},prepend:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.insertBefore(E,this.firstChild)}})},before:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this)})},after:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this.nextSibling)})},end:function(){return this.prevObject||o([])},push:[].push,sort:[].sort,splice:[].splice,find:function(E){if(this.length===1){var F=this.pushStack([],"find",E);F.length=0;o.find(E,this[0],F);return F}else{return this.pushStack(o.unique(o.map(this,function(G){return o.find(E,G)})),"find",E)}},clone:function(G){var E=this.map(function(){if(!o.support.noCloneEvent&&!o.isXMLDoc(this)){var I=this.outerHTML;if(!I){var J=this.ownerDocument.createElement("div");J.appendChild(this.cloneNode(true));I=J.innerHTML}return o.clean([I.replace(/ jQuery\d+="(?:\d+|null)"/g,"").replace(/^\s*/,"")])[0]}else{return this.cloneNode(true)}});if(G===true){var H=this.find("*").andSelf(),F=0;E.find("*").andSelf().each(function(){if(this.nodeName!==H[F].nodeName){return}var I=o.data(H[F],"events");for(var K in I){for(var J in I[K]){o.event.add(this,K,I[K][J],I[K][J].data)}}F++})}return E},filter:function(E){return this.pushStack(o.isFunction(E)&&o.grep(this,function(G,F){return E.call(G,F)})||o.multiFilter(E,o.grep(this,function(F){return F.nodeType===1})),"filter",E)},closest:function(E){var G=o.expr.match.POS.test(E)?o(E):null,F=0;return this.map(function(){var H=this;while(H&&H.ownerDocument){if(G?G.index(H)>-1:o(H).is(E)){o.data(H,"closest",F);return H}H=H.parentNode;F++}})},not:function(E){if(typeof E==="string"){if(f.test(E)){return this.pushStack(o.multiFilter(E,this,true),"not",E)}else{E=o.multiFilter(E,this)}}var F=E.length&&E[E.length-1]!==g&&!E.nodeType;return this.filter(function(){return F?o.inArray(this,E)<0:this!=E})},add:function(E){return this.pushStack(o.unique(o.merge(this.get(),typeof E==="string"?o(E):o.makeArray(E))))},is:function(E){return !!E&&o.multiFilter(E,this).length>0},hasClass:function(E){return !!E&&this.is("."+E)},val:function(K){if(K===g){var E=this[0];if(E){if(o.nodeName(E,"option")){return(E.attributes.value||{}).specified?E.value:E.text}if(o.nodeName(E,"select")){var I=E.selectedIndex,L=[],M=E.options,H=E.type=="select-one";if(I<0){return null}for(var F=H?I:0,J=H?I+1:M.length;F=0||o.inArray(this.name,K)>=0)}else{if(o.nodeName(this,"select")){var N=o.makeArray(K);o("option",this).each(function(){this.selected=(o.inArray(this.value,N)>=0||o.inArray(this.text,N)>=0)});if(!N.length){this.selectedIndex=-1}}else{this.value=K}}})},html:function(E){return E===g?(this[0]?this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g,""):null):this.empty().append(E)},replaceWith:function(E){return this.after(E).remove()},eq:function(E){return this.slice(E,+E+1)},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments),"slice",Array.prototype.slice.call(arguments).join(","))},map:function(E){return this.pushStack(o.map(this,function(G,F){return E.call(G,F,G)}))},andSelf:function(){return this.add(this.prevObject)},domManip:function(J,M,L){if(this[0]){var I=(this[0].ownerDocument||this[0]).createDocumentFragment(),F=o.clean(J,(this[0].ownerDocument||this[0]),I),H=I.firstChild;if(H){for(var G=0,E=this.length;G1||G>0?I.cloneNode(true):I)}}if(F){o.each(F,z)}}return this;function K(N,O){return M&&o.nodeName(N,"table")&&o.nodeName(O,"tr")?(N.getElementsByTagName("tbody")[0]||N.appendChild(N.ownerDocument.createElement("tbody"))):N}}};o.fn.init.prototype=o.fn;function z(E,F){if(F.src){o.ajax({url:F.src,async:false,dataType:"script"})}else{o.globalEval(F.text||F.textContent||F.innerHTML||"")}if(F.parentNode){F.parentNode.removeChild(F)}}function e(){return +new Date}o.extend=o.fn.extend=function(){var J=arguments[0]||{},H=1,I=arguments.length,E=false,G;if(typeof J==="boolean"){E=J;J=arguments[1]||{};H=2}if(typeof J!=="object"&&!o.isFunction(J)){J={}}if(I==H){J=this;--H}for(;H-1}},swap:function(H,G,I){var E={};for(var F in G){E[F]=H.style[F];H.style[F]=G[F]}I.call(H);for(var F in G){H.style[F]=E[F]}},css:function(H,F,J,E){if(F=="width"||F=="height"){var L,G={position:"absolute",visibility:"hidden",display:"block"},K=F=="width"?["Left","Right"]:["Top","Bottom"];function I(){L=F=="width"?H.offsetWidth:H.offsetHeight;if(E==="border"){return}o.each(K,function(){if(!E){L-=parseFloat(o.curCSS(H,"padding"+this,true))||0}if(E==="margin"){L+=parseFloat(o.curCSS(H,"margin"+this,true))||0}else{L-=parseFloat(o.curCSS(H,"border"+this+"Width",true))||0}})}if(H.offsetWidth!==0){I()}else{o.swap(H,G,I)}return Math.max(0,Math.round(L))}return o.curCSS(H,F,J)},curCSS:function(I,F,G){var L,E=I.style;if(F=="opacity"&&!o.support.opacity){L=o.attr(E,"opacity");return L==""?"1":L}if(F.match(/float/i)){F=w}if(!G&&E&&E[F]){L=E[F]}else{if(q.getComputedStyle){if(F.match(/float/i)){F="float"}F=F.replace(/([A-Z])/g,"-$1").toLowerCase();var M=q.getComputedStyle(I,null);if(M){L=M.getPropertyValue(F)}if(F=="opacity"&&L==""){L="1"}}else{if(I.currentStyle){var J=F.replace(/\-(\w)/g,function(N,O){return O.toUpperCase()});L=I.currentStyle[F]||I.currentStyle[J];if(!/^\d+(px)?$/i.test(L)&&/^\d/.test(L)){var H=E.left,K=I.runtimeStyle.left;I.runtimeStyle.left=I.currentStyle.left;E.left=L||0;L=E.pixelLeft+"px";E.left=H;I.runtimeStyle.left=K}}}}return L},clean:function(F,K,I){K=K||document;if(typeof K.createElement==="undefined"){K=K.ownerDocument||K[0]&&K[0].ownerDocument||document}if(!I&&F.length===1&&typeof F[0]==="string"){var H=/^<(\w+)\s*\/?>$/.exec(F[0]);if(H){return[K.createElement(H[1])]}}var G=[],E=[],L=K.createElement("div");o.each(F,function(P,S){if(typeof S==="number"){S+=""}if(!S){return}if(typeof S==="string"){S=S.replace(/(<(\w+)[^>]*?)\/>/g,function(U,V,T){return T.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?U:V+">"});var O=S.replace(/^\s+/,"").substring(0,10).toLowerCase();var Q=!O.indexOf("",""]||!O.indexOf("",""]||O.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"","
"]||!O.indexOf("",""]||(!O.indexOf("",""]||!O.indexOf("",""]||!o.support.htmlSerialize&&[1,"div
","
"]||[0,"",""];L.innerHTML=Q[1]+S+Q[2];while(Q[0]--){L=L.lastChild}if(!o.support.tbody){var R=/"&&!R?L.childNodes:[];for(var M=N.length-1;M>=0;--M){if(o.nodeName(N[M],"tbody")&&!N[M].childNodes.length){N[M].parentNode.removeChild(N[M])}}}if(!o.support.leadingWhitespace&&/^\s/.test(S)){L.insertBefore(K.createTextNode(S.match(/^\s*/)[0]),L.firstChild)}S=o.makeArray(L.childNodes)}if(S.nodeType){G.push(S)}else{G=o.merge(G,S)}});if(I){for(var J=0;G[J];J++){if(o.nodeName(G[J],"script")&&(!G[J].type||G[J].type.toLowerCase()==="text/javascript")){E.push(G[J].parentNode?G[J].parentNode.removeChild(G[J]):G[J])}else{if(G[J].nodeType===1){G.splice.apply(G,[J+1,0].concat(o.makeArray(G[J].getElementsByTagName("script"))))}I.appendChild(G[J])}}return E}return G},attr:function(J,G,K){if(!J||J.nodeType==3||J.nodeType==8){return g}var H=!o.isXMLDoc(J),L=K!==g;G=H&&o.props[G]||G;if(J.tagName){var F=/href|src|style/.test(G);if(G=="selected"&&J.parentNode){J.parentNode.selectedIndex}if(G in J&&H&&!F){if(L){if(G=="type"&&o.nodeName(J,"input")&&J.parentNode){throw"type property can't be changed"}J[G]=K}if(o.nodeName(J,"form")&&J.getAttributeNode(G)){return J.getAttributeNode(G).nodeValue}if(G=="tabIndex"){var I=J.getAttributeNode("tabIndex");return I&&I.specified?I.value:J.nodeName.match(/(button|input|object|select|textarea)/i)?0:J.nodeName.match(/^(a|area)$/i)&&J.href?0:g}return J[G]}if(!o.support.style&&H&&G=="style"){return o.attr(J.style,"cssText",K)}if(L){J.setAttribute(G,""+K)}var E=!o.support.hrefNormalized&&H&&F?J.getAttribute(G,2):J.getAttribute(G);return E===null?g:E}if(!o.support.opacity&&G=="opacity"){if(L){J.zoom=1;J.filter=(J.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(K)+""=="NaN"?"":"alpha(opacity="+K*100+")")}return J.filter&&J.filter.indexOf("opacity=")>=0?(parseFloat(J.filter.match(/opacity=([^)]*)/)[1])/100)+"":""}G=G.replace(/-([a-z])/ig,function(M,N){return N.toUpperCase()});if(L){J[G]=K}return J[G]},trim:function(E){return(E||"").replace(/^\s+|\s+$/g,"")},makeArray:function(G){var E=[];if(G!=null){var F=G.length;if(F==null||typeof G==="string"||o.isFunction(G)||G.setInterval){E[0]=G}else{while(F){E[--F]=G[F]}}}return E},inArray:function(G,H){for(var E=0,F=H.length;E0?this.clone(true):this).get();o.fn[F].apply(o(L[K]),I);J=J.concat(I)}return this.pushStack(J,E,G)}});o.each({removeAttr:function(E){o.attr(this,E,"");if(this.nodeType==1){this.removeAttribute(E)}},addClass:function(E){o.className.add(this,E)},removeClass:function(E){o.className.remove(this,E)},toggleClass:function(F,E){if(typeof E!=="boolean"){E=!o.className.has(this,F)}o.className[E?"add":"remove"](this,F)},remove:function(E){if(!E||o.filter(E,[this]).length){o("*",this).add([this]).each(function(){o.event.remove(this);o.removeData(this)});if(this.parentNode){this.parentNode.removeChild(this)}}},empty:function(){o(this).children().remove();while(this.firstChild){this.removeChild(this.firstChild)}}},function(E,F){o.fn[E]=function(){return this.each(F,arguments)}});function j(E,F){return E[0]&&parseInt(o.curCSS(E[0],F,true),10)||0}var h="jQuery"+e(),v=0,A={};o.extend({cache:{},data:function(F,E,G){F=F==l?A:F;var H=F[h];if(!H){H=F[h]=++v}if(E&&!o.cache[H]){o.cache[H]={}}if(G!==g){o.cache[H][E]=G}return E?o.cache[H][E]:H},removeData:function(F,E){F=F==l?A:F;var H=F[h];if(E){if(o.cache[H]){delete o.cache[H][E];E="";for(E in o.cache[H]){break}if(!E){o.removeData(F)}}}else{try{delete F[h]}catch(G){if(F.removeAttribute){F.removeAttribute(h)}}delete o.cache[H]}},queue:function(F,E,H){if(F){E=(E||"fx")+"queue";var G=o.data(F,E);if(!G||o.isArray(H)){G=o.data(F,E,o.makeArray(H))}else{if(H){G.push(H)}}}return G},dequeue:function(H,G){var E=o.queue(H,G),F=E.shift();if(!G||G==="fx"){F=E[0]}if(F!==g){F.call(H)}}});o.fn.extend({data:function(E,G){var H=E.split(".");H[1]=H[1]?"."+H[1]:"";if(G===g){var F=this.triggerHandler("getData"+H[1]+"!",[H[0]]);if(F===g&&this.length){F=o.data(this[0],E)}return F===g&&H[1]?this.data(H[0]):F}else{return this.trigger("setData"+H[1]+"!",[H[0],G]).each(function(){o.data(this,E,G)})}},removeData:function(E){return this.each(function(){o.removeData(this,E)})},queue:function(E,F){if(typeof E!=="string"){F=E;E="fx"}if(F===g){return o.queue(this[0],E)}return this.each(function(){var G=o.queue(this,E,F);if(E=="fx"&&G.length==1){G[0].call(this)}})},dequeue:function(E){return this.each(function(){o.dequeue(this,E)})}}); 13 | /* 14 | * Sizzle CSS Selector Engine - v0.9.3 15 | * Copyright 2009, The Dojo Foundation 16 | * Released under the MIT, BSD, and GPL Licenses. 17 | * More information: http://sizzlejs.com/ 18 | */ 19 | (function(){var R=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,L=0,H=Object.prototype.toString;var F=function(Y,U,ab,ac){ab=ab||[];U=U||document;if(U.nodeType!==1&&U.nodeType!==9){return[]}if(!Y||typeof Y!=="string"){return ab}var Z=[],W,af,ai,T,ad,V,X=true;R.lastIndex=0;while((W=R.exec(Y))!==null){Z.push(W[1]);if(W[2]){V=RegExp.rightContext;break}}if(Z.length>1&&M.exec(Y)){if(Z.length===2&&I.relative[Z[0]]){af=J(Z[0]+Z[1],U)}else{af=I.relative[Z[0]]?[U]:F(Z.shift(),U);while(Z.length){Y=Z.shift();if(I.relative[Y]){Y+=Z.shift()}af=J(Y,af)}}}else{var ae=ac?{expr:Z.pop(),set:E(ac)}:F.find(Z.pop(),Z.length===1&&U.parentNode?U.parentNode:U,Q(U));af=F.filter(ae.expr,ae.set);if(Z.length>0){ai=E(af)}else{X=false}while(Z.length){var ah=Z.pop(),ag=ah;if(!I.relative[ah]){ah=""}else{ag=Z.pop()}if(ag==null){ag=U}I.relative[ah](ai,ag,Q(U))}}if(!ai){ai=af}if(!ai){throw"Syntax error, unrecognized expression: "+(ah||Y)}if(H.call(ai)==="[object Array]"){if(!X){ab.push.apply(ab,ai)}else{if(U.nodeType===1){for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&(ai[aa]===true||ai[aa].nodeType===1&&K(U,ai[aa]))){ab.push(af[aa])}}}else{for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&ai[aa].nodeType===1){ab.push(af[aa])}}}}}else{E(ai,ab)}if(V){F(V,U,ab,ac);if(G){hasDuplicate=false;ab.sort(G);if(hasDuplicate){for(var aa=1;aa":function(Z,U,aa){var X=typeof U==="string";if(X&&!/\W/.test(U)){U=aa?U:U.toUpperCase();for(var V=0,T=Z.length;V=0)){if(!V){T.push(Y)}}else{if(V){U[X]=false}}}}return false},ID:function(T){return T[1].replace(/\\/g,"")},TAG:function(U,T){for(var V=0;T[V]===false;V++){}return T[V]&&Q(T[V])?U[1]:U[1].toUpperCase()},CHILD:function(T){if(T[1]=="nth"){var U=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(T[2]=="even"&&"2n"||T[2]=="odd"&&"2n+1"||!/\D/.test(T[2])&&"0n+"+T[2]||T[2]);T[2]=(U[1]+(U[2]||1))-0;T[3]=U[3]-0}T[0]=L++;return T},ATTR:function(X,U,V,T,Y,Z){var W=X[1].replace(/\\/g,"");if(!Z&&I.attrMap[W]){X[1]=I.attrMap[W]}if(X[2]==="~="){X[4]=" "+X[4]+" "}return X},PSEUDO:function(X,U,V,T,Y){if(X[1]==="not"){if(X[3].match(R).length>1||/^\w/.test(X[3])){X[3]=F(X[3],null,null,U)}else{var W=F.filter(X[3],U,V,true^Y);if(!V){T.push.apply(T,W)}return false}}else{if(I.match.POS.test(X[0])||I.match.CHILD.test(X[0])){return true}}return X},POS:function(T){T.unshift(true);return T}},filters:{enabled:function(T){return T.disabled===false&&T.type!=="hidden"},disabled:function(T){return T.disabled===true},checked:function(T){return T.checked===true},selected:function(T){T.parentNode.selectedIndex;return T.selected===true},parent:function(T){return !!T.firstChild},empty:function(T){return !T.firstChild},has:function(V,U,T){return !!F(T[3],V).length},header:function(T){return/h\d/i.test(T.nodeName)},text:function(T){return"text"===T.type},radio:function(T){return"radio"===T.type},checkbox:function(T){return"checkbox"===T.type},file:function(T){return"file"===T.type},password:function(T){return"password"===T.type},submit:function(T){return"submit"===T.type},image:function(T){return"image"===T.type},reset:function(T){return"reset"===T.type},button:function(T){return"button"===T.type||T.nodeName.toUpperCase()==="BUTTON"},input:function(T){return/input|select|textarea|button/i.test(T.nodeName)}},setFilters:{first:function(U,T){return T===0},last:function(V,U,T,W){return U===W.length-1},even:function(U,T){return T%2===0},odd:function(U,T){return T%2===1},lt:function(V,U,T){return UT[3]-0},nth:function(V,U,T){return T[3]-0==U},eq:function(V,U,T){return T[3]-0==U}},filter:{PSEUDO:function(Z,V,W,aa){var U=V[1],X=I.filters[U];if(X){return X(Z,W,V,aa)}else{if(U==="contains"){return(Z.textContent||Z.innerText||"").indexOf(V[3])>=0}else{if(U==="not"){var Y=V[3];for(var W=0,T=Y.length;W=0)}}},ID:function(U,T){return U.nodeType===1&&U.getAttribute("id")===T},TAG:function(U,T){return(T==="*"&&U.nodeType===1)||U.nodeName===T},CLASS:function(U,T){return(" "+(U.className||U.getAttribute("class"))+" ").indexOf(T)>-1},ATTR:function(Y,W){var V=W[1],T=I.attrHandle[V]?I.attrHandle[V](Y):Y[V]!=null?Y[V]:Y.getAttribute(V),Z=T+"",X=W[2],U=W[4];return T==null?X==="!=":X==="="?Z===U:X==="*="?Z.indexOf(U)>=0:X==="~="?(" "+Z+" ").indexOf(U)>=0:!U?Z&&T!==false:X==="!="?Z!=U:X==="^="?Z.indexOf(U)===0:X==="$="?Z.substr(Z.length-U.length)===U:X==="|="?Z===U||Z.substr(0,U.length+1)===U+"-":false},POS:function(X,U,V,Y){var T=U[2],W=I.setFilters[T];if(W){return W(X,V,U,Y)}}}};var M=I.match.POS;for(var O in I.match){I.match[O]=RegExp(I.match[O].source+/(?![^\[]*\])(?![^\(]*\))/.source)}var E=function(U,T){U=Array.prototype.slice.call(U);if(T){T.push.apply(T,U);return T}return U};try{Array.prototype.slice.call(document.documentElement.childNodes)}catch(N){E=function(X,W){var U=W||[];if(H.call(X)==="[object Array]"){Array.prototype.push.apply(U,X)}else{if(typeof X.length==="number"){for(var V=0,T=X.length;V";var T=document.documentElement;T.insertBefore(U,T.firstChild);if(!!document.getElementById(V)){I.find.ID=function(X,Y,Z){if(typeof Y.getElementById!=="undefined"&&!Z){var W=Y.getElementById(X[1]);return W?W.id===X[1]||typeof W.getAttributeNode!=="undefined"&&W.getAttributeNode("id").nodeValue===X[1]?[W]:g:[]}};I.filter.ID=function(Y,W){var X=typeof Y.getAttributeNode!=="undefined"&&Y.getAttributeNode("id");return Y.nodeType===1&&X&&X.nodeValue===W}}T.removeChild(U)})();(function(){var T=document.createElement("div");T.appendChild(document.createComment(""));if(T.getElementsByTagName("*").length>0){I.find.TAG=function(U,Y){var X=Y.getElementsByTagName(U[1]);if(U[1]==="*"){var W=[];for(var V=0;X[V];V++){if(X[V].nodeType===1){W.push(X[V])}}X=W}return X}}T.innerHTML="";if(T.firstChild&&typeof T.firstChild.getAttribute!=="undefined"&&T.firstChild.getAttribute("href")!=="#"){I.attrHandle.href=function(U){return U.getAttribute("href",2)}}})();if(document.querySelectorAll){(function(){var T=F,U=document.createElement("div");U.innerHTML="

";if(U.querySelectorAll&&U.querySelectorAll(".TEST").length===0){return}F=function(Y,X,V,W){X=X||document;if(!W&&X.nodeType===9&&!Q(X)){try{return E(X.querySelectorAll(Y),V)}catch(Z){}}return T(Y,X,V,W)};F.find=T.find;F.filter=T.filter;F.selectors=T.selectors;F.matches=T.matches})()}if(document.getElementsByClassName&&document.documentElement.getElementsByClassName){(function(){var T=document.createElement("div");T.innerHTML="
";if(T.getElementsByClassName("e").length===0){return}T.lastChild.className="e";if(T.getElementsByClassName("e").length===1){return}I.order.splice(1,0,"CLASS");I.find.CLASS=function(U,V,W){if(typeof V.getElementsByClassName!=="undefined"&&!W){return V.getElementsByClassName(U[1])}}})()}function P(U,Z,Y,ad,aa,ac){var ab=U=="previousSibling"&&!ac;for(var W=0,V=ad.length;W0){X=T;break}}}T=T[U]}ad[W]=X}}}var K=document.compareDocumentPosition?function(U,T){return U.compareDocumentPosition(T)&16}:function(U,T){return U!==T&&(U.contains?U.contains(T):true)};var Q=function(T){return T.nodeType===9&&T.documentElement.nodeName!=="HTML"||!!T.ownerDocument&&Q(T.ownerDocument)};var J=function(T,aa){var W=[],X="",Y,V=aa.nodeType?[aa]:aa;while((Y=I.match.PSEUDO.exec(T))){X+=Y[0];T=T.replace(I.match.PSEUDO,"")}T=I.relative[T]?T+"*":T;for(var Z=0,U=V.length;Z0||T.offsetHeight>0};F.selectors.filters.animated=function(T){return o.grep(o.timers,function(U){return T===U.elem}).length};o.multiFilter=function(V,T,U){if(U){V=":not("+V+")"}return F.matches(V,T)};o.dir=function(V,U){var T=[],W=V[U];while(W&&W!=document){if(W.nodeType==1){T.push(W)}W=W[U]}return T};o.nth=function(X,T,V,W){T=T||1;var U=0;for(;X;X=X[V]){if(X.nodeType==1&&++U==T){break}}return X};o.sibling=function(V,U){var T=[];for(;V;V=V.nextSibling){if(V.nodeType==1&&V!=U){T.push(V)}}return T};return;l.Sizzle=F})();o.event={add:function(I,F,H,K){if(I.nodeType==3||I.nodeType==8){return}if(I.setInterval&&I!=l){I=l}if(!H.guid){H.guid=this.guid++}if(K!==g){var G=H;H=this.proxy(G);H.data=K}var E=o.data(I,"events")||o.data(I,"events",{}),J=o.data(I,"handle")||o.data(I,"handle",function(){return typeof o!=="undefined"&&!o.event.triggered?o.event.handle.apply(arguments.callee.elem,arguments):g});J.elem=I;o.each(F.split(/\s+/),function(M,N){var O=N.split(".");N=O.shift();H.type=O.slice().sort().join(".");var L=E[N];if(o.event.specialAll[N]){o.event.specialAll[N].setup.call(I,K,O)}if(!L){L=E[N]={};if(!o.event.special[N]||o.event.special[N].setup.call(I,K,O)===false){if(I.addEventListener){I.addEventListener(N,J,false)}else{if(I.attachEvent){I.attachEvent("on"+N,J)}}}}L[H.guid]=H;o.event.global[N]=true});I=null},guid:1,global:{},remove:function(K,H,J){if(K.nodeType==3||K.nodeType==8){return}var G=o.data(K,"events"),F,E;if(G){if(H===g||(typeof H==="string"&&H.charAt(0)==".")){for(var I in G){this.remove(K,I+(H||""))}}else{if(H.type){J=H.handler;H=H.type}o.each(H.split(/\s+/),function(M,O){var Q=O.split(".");O=Q.shift();var N=RegExp("(^|\\.)"+Q.slice().sort().join(".*\\.")+"(\\.|$)");if(G[O]){if(J){delete G[O][J.guid]}else{for(var P in G[O]){if(N.test(G[O][P].type)){delete G[O][P]}}}if(o.event.specialAll[O]){o.event.specialAll[O].teardown.call(K,Q)}for(F in G[O]){break}if(!F){if(!o.event.special[O]||o.event.special[O].teardown.call(K,Q)===false){if(K.removeEventListener){K.removeEventListener(O,o.data(K,"handle"),false)}else{if(K.detachEvent){K.detachEvent("on"+O,o.data(K,"handle"))}}}F=null;delete G[O]}}})}for(F in G){break}if(!F){var L=o.data(K,"handle");if(L){L.elem=null}o.removeData(K,"events");o.removeData(K,"handle")}}},trigger:function(I,K,H,E){var G=I.type||I;if(!E){I=typeof I==="object"?I[h]?I:o.extend(o.Event(G),I):o.Event(G);if(G.indexOf("!")>=0){I.type=G=G.slice(0,-1);I.exclusive=true}if(!H){I.stopPropagation();if(this.global[G]){o.each(o.cache,function(){if(this.events&&this.events[G]){o.event.trigger(I,K,this.handle.elem)}})}}if(!H||H.nodeType==3||H.nodeType==8){return g}I.result=g;I.target=H;K=o.makeArray(K);K.unshift(I)}I.currentTarget=H;var J=o.data(H,"handle");if(J){J.apply(H,K)}if((!H[G]||(o.nodeName(H,"a")&&G=="click"))&&H["on"+G]&&H["on"+G].apply(H,K)===false){I.result=false}if(!E&&H[G]&&!I.isDefaultPrevented()&&!(o.nodeName(H,"a")&&G=="click")){this.triggered=true;try{H[G]()}catch(L){}}this.triggered=false;if(!I.isPropagationStopped()){var F=H.parentNode||H.ownerDocument;if(F){o.event.trigger(I,K,F,true)}}},handle:function(K){var J,E;K=arguments[0]=o.event.fix(K||l.event);K.currentTarget=this;var L=K.type.split(".");K.type=L.shift();J=!L.length&&!K.exclusive;var I=RegExp("(^|\\.)"+L.slice().sort().join(".*\\.")+"(\\.|$)");E=(o.data(this,"events")||{})[K.type];for(var G in E){var H=E[G];if(J||I.test(H.type)){K.handler=H;K.data=H.data;var F=H.apply(this,arguments);if(F!==g){K.result=F;if(F===false){K.preventDefault();K.stopPropagation()}}if(K.isImmediatePropagationStopped()){break}}}},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(H){if(H[h]){return H}var F=H;H=o.Event(F);for(var G=this.props.length,J;G;){J=this.props[--G];H[J]=F[J]}if(!H.target){H.target=H.srcElement||document}if(H.target.nodeType==3){H.target=H.target.parentNode}if(!H.relatedTarget&&H.fromElement){H.relatedTarget=H.fromElement==H.target?H.toElement:H.fromElement}if(H.pageX==null&&H.clientX!=null){var I=document.documentElement,E=document.body;H.pageX=H.clientX+(I&&I.scrollLeft||E&&E.scrollLeft||0)-(I.clientLeft||0);H.pageY=H.clientY+(I&&I.scrollTop||E&&E.scrollTop||0)-(I.clientTop||0)}if(!H.which&&((H.charCode||H.charCode===0)?H.charCode:H.keyCode)){H.which=H.charCode||H.keyCode}if(!H.metaKey&&H.ctrlKey){H.metaKey=H.ctrlKey}if(!H.which&&H.button){H.which=(H.button&1?1:(H.button&2?3:(H.button&4?2:0)))}return H},proxy:function(F,E){E=E||function(){return F.apply(this,arguments)};E.guid=F.guid=F.guid||E.guid||this.guid++;return E},special:{ready:{setup:B,teardown:function(){}}},specialAll:{live:{setup:function(E,F){o.event.add(this,F[0],c)},teardown:function(G){if(G.length){var E=0,F=RegExp("(^|\\.)"+G[0]+"(\\.|$)");o.each((o.data(this,"events").live||{}),function(){if(F.test(this.type)){E++}});if(E<1){o.event.remove(this,G[0],c)}}}}}};o.Event=function(E){if(!this.preventDefault){return new o.Event(E)}if(E&&E.type){this.originalEvent=E;this.type=E.type}else{this.type=E}this.timeStamp=e();this[h]=true};function k(){return false}function u(){return true}o.Event.prototype={preventDefault:function(){this.isDefaultPrevented=u;var E=this.originalEvent;if(!E){return}if(E.preventDefault){E.preventDefault()}E.returnValue=false},stopPropagation:function(){this.isPropagationStopped=u;var E=this.originalEvent;if(!E){return}if(E.stopPropagation){E.stopPropagation()}E.cancelBubble=true},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=u;this.stopPropagation()},isDefaultPrevented:k,isPropagationStopped:k,isImmediatePropagationStopped:k};var a=function(F){var E=F.relatedTarget;while(E&&E!=this){try{E=E.parentNode}catch(G){E=this}}if(E!=this){F.type=F.data;o.event.handle.apply(this,arguments)}};o.each({mouseover:"mouseenter",mouseout:"mouseleave"},function(F,E){o.event.special[E]={setup:function(){o.event.add(this,F,a,E)},teardown:function(){o.event.remove(this,F,a)}}});o.fn.extend({bind:function(F,G,E){return F=="unload"?this.one(F,G,E):this.each(function(){o.event.add(this,F,E||G,E&&G)})},one:function(G,H,F){var E=o.event.proxy(F||H,function(I){o(this).unbind(I,E);return(F||H).apply(this,arguments)});return this.each(function(){o.event.add(this,G,E,F&&H)})},unbind:function(F,E){return this.each(function(){o.event.remove(this,F,E)})},trigger:function(E,F){return this.each(function(){o.event.trigger(E,F,this)})},triggerHandler:function(E,G){if(this[0]){var F=o.Event(E);F.preventDefault();F.stopPropagation();o.event.trigger(F,G,this[0]);return F.result}},toggle:function(G){var E=arguments,F=1;while(F=0){var E=G.slice(I,G.length);G=G.slice(0,I)}var H="GET";if(J){if(o.isFunction(J)){K=J;J=null}else{if(typeof J==="object"){J=o.param(J);H="POST"}}}var F=this;o.ajax({url:G,type:H,dataType:"html",data:J,complete:function(M,L){if(L=="success"||L=="notmodified"){F.html(E?o("
").append(M.responseText.replace(//g,"")).find(E):M.responseText)}if(K){F.each(K,[M.responseText,L,M])}}});return this},serialize:function(){return o.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?o.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password|search/i.test(this.type))}).map(function(E,F){var G=o(this).val();return G==null?null:o.isArray(G)?o.map(G,function(I,H){return{name:F.name,value:I}}):{name:F.name,value:G}}).get()}});o.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(E,F){o.fn[F]=function(G){return this.bind(F,G)}});var r=e();o.extend({get:function(E,G,H,F){if(o.isFunction(G)){H=G;G=null}return o.ajax({type:"GET",url:E,data:G,success:H,dataType:F})},getScript:function(E,F){return o.get(E,null,F,"script")},getJSON:function(E,F,G){return o.get(E,F,G,"json")},post:function(E,G,H,F){if(o.isFunction(G)){H=G;G={}}return o.ajax({type:"POST",url:E,data:G,success:H,dataType:F})},ajaxSetup:function(E){o.extend(o.ajaxSettings,E)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return l.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest()},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(M){M=o.extend(true,M,o.extend(true,{},o.ajaxSettings,M));var W,F=/=\?(&|$)/g,R,V,G=M.type.toUpperCase();if(M.data&&M.processData&&typeof M.data!=="string"){M.data=o.param(M.data)}if(M.dataType=="jsonp"){if(G=="GET"){if(!M.url.match(F)){M.url+=(M.url.match(/\?/)?"&":"?")+(M.jsonp||"callback")+"=?"}}else{if(!M.data||!M.data.match(F)){M.data=(M.data?M.data+"&":"")+(M.jsonp||"callback")+"=?"}}M.dataType="json"}if(M.dataType=="json"&&(M.data&&M.data.match(F)||M.url.match(F))){W="jsonp"+r++;if(M.data){M.data=(M.data+"").replace(F,"="+W+"$1")}M.url=M.url.replace(F,"="+W+"$1");M.dataType="script";l[W]=function(X){V=X;I();L();l[W]=g;try{delete l[W]}catch(Y){}if(H){H.removeChild(T)}}}if(M.dataType=="script"&&M.cache==null){M.cache=false}if(M.cache===false&&G=="GET"){var E=e();var U=M.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+E+"$2");M.url=U+((U==M.url)?(M.url.match(/\?/)?"&":"?")+"_="+E:"")}if(M.data&&G=="GET"){M.url+=(M.url.match(/\?/)?"&":"?")+M.data;M.data=null}if(M.global&&!o.active++){o.event.trigger("ajaxStart")}var Q=/^(\w+:)?\/\/([^\/?#]+)/.exec(M.url);if(M.dataType=="script"&&G=="GET"&&Q&&(Q[1]&&Q[1]!=location.protocol||Q[2]!=location.host)){var H=document.getElementsByTagName("head")[0];var T=document.createElement("script");T.src=M.url;if(M.scriptCharset){T.charset=M.scriptCharset}if(!W){var O=false;T.onload=T.onreadystatechange=function(){if(!O&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){O=true;I();L();T.onload=T.onreadystatechange=null;H.removeChild(T)}}}H.appendChild(T);return g}var K=false;var J=M.xhr();if(M.username){J.open(G,M.url,M.async,M.username,M.password)}else{J.open(G,M.url,M.async)}try{if(M.data){J.setRequestHeader("Content-Type",M.contentType)}if(M.ifModified){J.setRequestHeader("If-Modified-Since",o.lastModified[M.url]||"Thu, 01 Jan 1970 00:00:00 GMT")}J.setRequestHeader("X-Requested-With","XMLHttpRequest");J.setRequestHeader("Accept",M.dataType&&M.accepts[M.dataType]?M.accepts[M.dataType]+", */*":M.accepts._default)}catch(S){}if(M.beforeSend&&M.beforeSend(J,M)===false){if(M.global&&!--o.active){o.event.trigger("ajaxStop")}J.abort();return false}if(M.global){o.event.trigger("ajaxSend",[J,M])}var N=function(X){if(J.readyState==0){if(P){clearInterval(P);P=null;if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}}else{if(!K&&J&&(J.readyState==4||X=="timeout")){K=true;if(P){clearInterval(P);P=null}R=X=="timeout"?"timeout":!o.httpSuccess(J)?"error":M.ifModified&&o.httpNotModified(J,M.url)?"notmodified":"success";if(R=="success"){try{V=o.httpData(J,M.dataType,M)}catch(Z){R="parsererror"}}if(R=="success"){var Y;try{Y=J.getResponseHeader("Last-Modified")}catch(Z){}if(M.ifModified&&Y){o.lastModified[M.url]=Y}if(!W){I()}}else{o.handleError(M,J,R)}L();if(X){J.abort()}if(M.async){J=null}}}};if(M.async){var P=setInterval(N,13);if(M.timeout>0){setTimeout(function(){if(J&&!K){N("timeout")}},M.timeout)}}try{J.send(M.data)}catch(S){o.handleError(M,J,null,S)}if(!M.async){N()}function I(){if(M.success){M.success(V,R)}if(M.global){o.event.trigger("ajaxSuccess",[J,M])}}function L(){if(M.complete){M.complete(J,R)}if(M.global){o.event.trigger("ajaxComplete",[J,M])}if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}return J},handleError:function(F,H,E,G){if(F.error){F.error(H,E,G)}if(F.global){o.event.trigger("ajaxError",[H,F,G])}},active:0,httpSuccess:function(F){try{return !F.status&&location.protocol=="file:"||(F.status>=200&&F.status<300)||F.status==304||F.status==1223}catch(E){}return false},httpNotModified:function(G,E){try{var H=G.getResponseHeader("Last-Modified");return G.status==304||H==o.lastModified[E]}catch(F){}return false},httpData:function(J,H,G){var F=J.getResponseHeader("content-type"),E=H=="xml"||!H&&F&&F.indexOf("xml")>=0,I=E?J.responseXML:J.responseText;if(E&&I.documentElement.tagName=="parsererror"){throw"parsererror"}if(G&&G.dataFilter){I=G.dataFilter(I,H)}if(typeof I==="string"){if(H=="script"){o.globalEval(I)}if(H=="json"){I=l["eval"]("("+I+")")}}return I},param:function(E){var G=[];function H(I,J){G[G.length]=encodeURIComponent(I)+"="+encodeURIComponent(J)}if(o.isArray(E)||E.jquery){o.each(E,function(){H(this.name,this.value)})}else{for(var F in E){if(o.isArray(E[F])){o.each(E[F],function(){H(F,this)})}else{H(F,o.isFunction(E[F])?E[F]():E[F])}}}return G.join("&").replace(/%20/g,"+")}});var m={},n,d=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];function t(F,E){var G={};o.each(d.concat.apply([],d.slice(0,E)),function(){G[this]=F});return G}o.fn.extend({show:function(J,L){if(J){return this.animate(t("show",3),J,L)}else{for(var H=0,F=this.length;H").appendTo("body");K=I.css("display");if(K==="none"){K="block"}I.remove();m[G]=K}o.data(this[H],"olddisplay",K)}}for(var H=0,F=this.length;H=0;H--){if(G[H].elem==this){if(E){G[H](true)}G.splice(H,1)}}});if(!E){this.dequeue()}return this}});o.each({slideDown:t("show",1),slideUp:t("hide",1),slideToggle:t("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(E,F){o.fn[E]=function(G,H){return this.animate(F,G,H)}});o.extend({speed:function(G,H,F){var E=typeof G==="object"?G:{complete:F||!F&&H||o.isFunction(G)&&G,duration:G,easing:F&&H||H&&!o.isFunction(H)&&H};E.duration=o.fx.off?0:typeof E.duration==="number"?E.duration:o.fx.speeds[E.duration]||o.fx.speeds._default;E.old=E.complete;E.complete=function(){if(E.queue!==false){o(this).dequeue()}if(o.isFunction(E.old)){E.old.call(this)}};return E},easing:{linear:function(G,H,E,F){return E+F*G},swing:function(G,H,E,F){return((-Math.cos(G*Math.PI)/2)+0.5)*F+E}},timers:[],fx:function(F,E,G){this.options=E;this.elem=F;this.prop=G;if(!E.orig){E.orig={}}}});o.fx.prototype={update:function(){if(this.options.step){this.options.step.call(this.elem,this.now,this)}(o.fx.step[this.prop]||o.fx.step._default)(this);if((this.prop=="height"||this.prop=="width")&&this.elem.style){this.elem.style.display="block"}},cur:function(F){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null)){return this.elem[this.prop]}var E=parseFloat(o.css(this.elem,this.prop,F));return E&&E>-10000?E:parseFloat(o.curCSS(this.elem,this.prop))||0},custom:function(I,H,G){this.startTime=e();this.start=I;this.end=H;this.unit=G||this.unit||"px";this.now=this.start;this.pos=this.state=0;var E=this;function F(J){return E.step(J)}F.elem=this.elem;if(F()&&o.timers.push(F)&&!n){n=setInterval(function(){var K=o.timers;for(var J=0;J=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var E=true;for(var F in this.options.curAnim){if(this.options.curAnim[F]!==true){E=false}}if(E){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(o.css(this.elem,"display")=="none"){this.elem.style.display="block"}}if(this.options.hide){o(this.elem).hide()}if(this.options.hide||this.options.show){for(var I in this.options.curAnim){o.attr(this.elem.style,I,this.options.orig[I])}}this.options.complete.call(this.elem)}return false}else{var J=G-this.startTime;this.state=J/this.options.duration;this.pos=o.easing[this.options.easing||(o.easing.swing?"swing":"linear")](this.state,J,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update()}return true}};o.extend(o.fx,{speeds:{slow:600,fast:200,_default:400},step:{opacity:function(E){o.attr(E.elem.style,"opacity",E.now)},_default:function(E){if(E.elem.style&&E.elem.style[E.prop]!=null){E.elem.style[E.prop]=E.now+E.unit}else{E.elem[E.prop]=E.now}}}});if(document.documentElement.getBoundingClientRect){o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}var G=this[0].getBoundingClientRect(),J=this[0].ownerDocument,F=J.body,E=J.documentElement,L=E.clientTop||F.clientTop||0,K=E.clientLeft||F.clientLeft||0,I=G.top+(self.pageYOffset||o.boxModel&&E.scrollTop||F.scrollTop)-L,H=G.left+(self.pageXOffset||o.boxModel&&E.scrollLeft||F.scrollLeft)-K;return{top:I,left:H}}}else{o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}o.offset.initialized||o.offset.initialize();var J=this[0],G=J.offsetParent,F=J,O=J.ownerDocument,M,H=O.documentElement,K=O.body,L=O.defaultView,E=L.getComputedStyle(J,null),N=J.offsetTop,I=J.offsetLeft;while((J=J.parentNode)&&J!==K&&J!==H){M=L.getComputedStyle(J,null);N-=J.scrollTop,I-=J.scrollLeft;if(J===G){N+=J.offsetTop,I+=J.offsetLeft;if(o.offset.doesNotAddBorder&&!(o.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(J.tagName))){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}F=G,G=J.offsetParent}if(o.offset.subtractsBorderForOverflowNotVisible&&M.overflow!=="visible"){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}E=M}if(E.position==="relative"||E.position==="static"){N+=K.offsetTop,I+=K.offsetLeft}if(E.position==="fixed"){N+=Math.max(H.scrollTop,K.scrollTop),I+=Math.max(H.scrollLeft,K.scrollLeft)}return{top:N,left:I}}}o.offset={initialize:function(){if(this.initialized){return}var L=document.body,F=document.createElement("div"),H,G,N,I,M,E,J=L.style.marginTop,K='
';M={position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"};for(E in M){F.style[E]=M[E]}F.innerHTML=K;L.insertBefore(F,L.firstChild);H=F.firstChild,G=H.firstChild,I=H.nextSibling.firstChild.firstChild;this.doesNotAddBorder=(G.offsetTop!==5);this.doesAddBorderForTableAndCells=(I.offsetTop===5);H.style.overflow="hidden",H.style.position="relative";this.subtractsBorderForOverflowNotVisible=(G.offsetTop===-5);L.style.marginTop="1px";this.doesNotIncludeMarginInBodyOffset=(L.offsetTop===0);L.style.marginTop=J;L.removeChild(F);this.initialized=true},bodyOffset:function(E){o.offset.initialized||o.offset.initialize();var G=E.offsetTop,F=E.offsetLeft;if(o.offset.doesNotIncludeMarginInBodyOffset){G+=parseInt(o.curCSS(E,"marginTop",true),10)||0,F+=parseInt(o.curCSS(E,"marginLeft",true),10)||0}return{top:G,left:F}}};o.fn.extend({position:function(){var I=0,H=0,F;if(this[0]){var G=this.offsetParent(),J=this.offset(),E=/^body|html$/i.test(G[0].tagName)?{top:0,left:0}:G.offset();J.top-=j(this,"marginTop");J.left-=j(this,"marginLeft");E.top+=j(G,"borderTopWidth");E.left+=j(G,"borderLeftWidth");F={top:J.top-E.top,left:J.left-E.left}}return F},offsetParent:function(){var E=this[0].offsetParent||document.body;while(E&&(!/^body|html$/i.test(E.tagName)&&o.css(E,"position")=="static")){E=E.offsetParent}return o(E)}});o.each(["Left","Top"],function(F,E){var G="scroll"+E;o.fn[G]=function(H){if(!this[0]){return null}return H!==g?this.each(function(){this==l||this==document?l.scrollTo(!F?H:o(l).scrollLeft(),F?H:o(l).scrollTop()):this[G]=H}):this[0]==l||this[0]==document?self[F?"pageYOffset":"pageXOffset"]||o.boxModel&&document.documentElement[G]||document.body[G]:this[0][G]}});o.each(["Height","Width"],function(I,G){var E=I?"Left":"Top",H=I?"Right":"Bottom",F=G.toLowerCase();o.fn["inner"+G]=function(){return this[0]?o.css(this[0],F,false,"padding"):null};o.fn["outer"+G]=function(K){return this[0]?o.css(this[0],F,false,K?"margin":"border"):null};var J=G.toLowerCase();o.fn[J]=function(K){return this[0]==l?document.compatMode=="CSS1Compat"&&document.documentElement["client"+G]||document.body["client"+G]:this[0]==document?Math.max(document.documentElement["client"+G],document.body["scroll"+G],document.documentElement["scroll"+G],document.body["offset"+G],document.documentElement["offset"+G]):K===g?(this.length?o.css(this[0],J):null):this.css(J,typeof K==="string"?K:K+"px")}})})(); -------------------------------------------------------------------------------- /jquery.parsley.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Utility implementation of node.sourceIndex() 3 | * 4 | * Returns the node's source-order position within the document. 5 | */ 6 | if(typeof document.documentElement.sourceIndex == "undefined") { 7 | HTMLElement.prototype.__defineGetter__("sourceIndex", (function(indexOf) { 8 | return function sourceIndex(){ 9 | return indexOf.call(this.ownerDocument.getElementsByTagName("*"), this); 10 | }; 11 | })(Array.prototype.indexOf)); 12 | } 13 | 14 | /** 15 | * Simple string/position tuple. Extracted strings need to retain 16 | * information about their place in the document for when we group 17 | * them later. 18 | */ 19 | function StringNode(string, position) { 20 | this.string = string; 21 | this.position = position; 22 | }; 23 | StringNode.prototype = new Object(); 24 | 25 | StringNode.prototype.toString = function() { 26 | return "<" + this.string + "(" + this.position + ")>"; 27 | } 28 | 29 | /** 30 | * This is similar to a jQuery list of HTML Nodes. I need to be able to 31 | * call methods on it, and each contained String needs an associated position. 32 | */ 33 | function StringNodeList() { 34 | this.multiple = false; 35 | }; 36 | StringNodeList.prototype = new Array(); 37 | 38 | StringNodeList.prototype.toString = function() { 39 | var buffer = " 0) buffer += ", "; 42 | buffer += this[i].toString(); 43 | } 44 | return buffer + "]>"; 45 | } 46 | 47 | /** 48 | * Creates an array of strings that mirrors this StringNodeList. 49 | */ 50 | StringNodeList.prototype.simple = function() { 51 | var array = []; 52 | jQuery.each(this, function() { 53 | array.push(this.string); 54 | }); 55 | return array; 56 | } 57 | 58 | /** 59 | * The core function is used to convert jQuery objects to 60 | * StringNodeLists. i.e.: 61 | * 62 | * var jq = jQuery('a.linky'); 63 | * var stringNodeList = jq.extract(function(domNode){ 64 | * // ... 65 | * return someStringFromInsideTheDomNode; 66 | * }); 67 | * 68 | * The following shortcut arguments are also available: 69 | * - extract() 70 | * => function(node) { return jQuery(node).text(); } 71 | * - extract("@foo") 72 | * => function(node) { return jQuery(node).attr("foo"); } 73 | * - extract(/regex/) 74 | * => function(node) { return (/regex/.execute(jQuery(node).text())[0]; } 75 | */ 76 | jQuery.fn.extract = function(func) { 77 | if(!func){ 78 | func = function(node) { 79 | return jQuery(node).text(); 80 | }; 81 | } 82 | 83 | // extract(/regex/) 84 | if(func instanceof RegExp) { 85 | var re = func; 86 | func = function(node) { 87 | var text = jQuery(node).text(); 88 | return re.exec(text)[0]; 89 | }; 90 | } 91 | 92 | // extract("@attribute") 93 | if(typeof(func) == "string" && func[0] == "@") { 94 | var attr = func.substring(1); 95 | func = function(node) { 96 | return jQuery(node).attr(attr); 97 | }; 98 | } 99 | 100 | var list = new StringNodeList(); 101 | this.each(function(){ 102 | list.push(new StringNode(func(this), this.sourceIndex)); 103 | }); 104 | return list; 105 | } 106 | 107 | function pQuery(){}; 108 | 109 | /** 110 | * Slightly voodoo. This function cleans up any remaining jQuery 111 | * objects by running extract on them, then tries to group all of the 112 | * StringNodeLists by their implicit page ordering. Then it simplifies 113 | * the resulting data structure to vanilla object/arrays. 114 | */ 115 | pQuery.extractAndGroup = function(parselet) { 116 | pQuery.extract(parselet); 117 | pQuery.group(parselet) 118 | return parselet; 119 | } 120 | 121 | /** 122 | * Builds a giant array of nodes as a pre-processing step for grouping. 123 | */ 124 | pQuery.compileNodes = function(object) { 125 | var nodes = []; 126 | jQuery.each(object, function(key, value) { 127 | if(value instanceof StringNodeList) { 128 | jQuery.each(value, function(i, node) { 129 | node.multiple = value.multiple; 130 | node.key = key; 131 | nodes.push(node); 132 | }); 133 | } 134 | }); 135 | return nodes.sort(function(a,b) { return a.position - b.position; }); 136 | } 137 | 138 | /** 139 | * This recursively looks for arrays with a single object child (i.e.: "[{...}]"). 140 | * This will get grouped. The actual grouping mechanics are: (1) make references in 141 | * each node to its key. (2) Throw all the nodes into a giant array. (3) Sort the 142 | * array in page order. (4) Iterate the array, dropping nodes into the current group. 143 | * (4a) Add an additional group if the current group is full. 144 | */ 145 | pQuery.group = function(parselet) { 146 | jQuery.each(parselet, function(key, value) { 147 | if(value instanceof Array && typeof(value[0]) == "object") { 148 | var allNodes = pQuery.compileNodes(value[0]); 149 | var node; 150 | var groups = []; 151 | var group = {}; 152 | groups.push(group); 153 | while(node = allNodes.shift()) { 154 | if(!node.multiple && group[node.key]){ 155 | group = {}; 156 | groups.push(group) 157 | } 158 | if(node.multiple) { 159 | if(!group[node.key]) group[node.key] = []; 160 | group[node.key].push(node.string); 161 | } else { 162 | group[node.key] = node.string; 163 | } 164 | } 165 | parselet[key] = groups; 166 | } else if(typeof(value) == "object" && !(value instanceof StringNodeList)) { 167 | pQuery.group(value); //recurse 168 | } 169 | }); 170 | } 171 | 172 | /** 173 | * This does the implied conversion from jQuery objects to StringNodeLists. 174 | * It also transforms the array: [$(...)] into a (Multiple) StringNodeList. 175 | * You shouldn't ever have to call this method; it's a preprocessing step 176 | * to extractAndGroup(). 177 | */ 178 | pQuery.extract = function(parselet) { 179 | jQuery.each(parselet, function(key, value) { 180 | if(typeof(value) == "array") { 181 | if(value[0] instanceof StringNodeList) { 182 | parselet[key] = value = value[0]; 183 | value.multiple = true; 184 | } else { 185 | pQuery.extract(value); 186 | } 187 | } else if(value instanceof jQuery) { 188 | parselet[key] = value = value.extract(); 189 | } else if(value instanceof StringNodeList) { 190 | //Nothing 191 | } else if(typeof(value) == "object") { 192 | pQuery.extract(value); 193 | } 194 | }); 195 | } -------------------------------------------------------------------------------- /json2.js: -------------------------------------------------------------------------------- 1 | /* 2 | http://www.JSON.org/json2.js 3 | 2009-09-29 4 | 5 | Public Domain. 6 | 7 | NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. 8 | 9 | See http://www.JSON.org/js.html 10 | 11 | 12 | This code should be minified before deployment. 13 | See http://javascript.crockford.com/jsmin.html 14 | 15 | USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO 16 | NOT CONTROL. 17 | 18 | 19 | This file creates a global JSON object containing two methods: stringify 20 | and parse. 21 | 22 | JSON.stringify(value, replacer, space) 23 | value any JavaScript value, usually an object or array. 24 | 25 | replacer an optional parameter that determines how object 26 | values are stringified for objects. It can be a 27 | function or an array of strings. 28 | 29 | space an optional parameter that specifies the indentation 30 | of nested structures. If it is omitted, the text will 31 | be packed without extra whitespace. If it is a number, 32 | it will specify the number of spaces to indent at each 33 | level. If it is a string (such as '\t' or ' '), 34 | it contains the characters used to indent at each level. 35 | 36 | This method produces a JSON text from a JavaScript value. 37 | 38 | When an object value is found, if the object contains a toJSON 39 | method, its toJSON method will be called and the result will be 40 | stringified. A toJSON method does not serialize: it returns the 41 | value represented by the name/value pair that should be serialized, 42 | or undefined if nothing should be serialized. The toJSON method 43 | will be passed the key associated with the value, and this will be 44 | bound to the value 45 | 46 | For example, this would serialize Dates as ISO strings. 47 | 48 | Date.prototype.toJSON = function (key) { 49 | function f(n) { 50 | // Format integers to have at least two digits. 51 | return n < 10 ? '0' + n : n; 52 | } 53 | 54 | return this.getUTCFullYear() + '-' + 55 | f(this.getUTCMonth() + 1) + '-' + 56 | f(this.getUTCDate()) + 'T' + 57 | f(this.getUTCHours()) + ':' + 58 | f(this.getUTCMinutes()) + ':' + 59 | f(this.getUTCSeconds()) + 'Z'; 60 | }; 61 | 62 | You can provide an optional replacer method. It will be passed the 63 | key and value of each member, with this bound to the containing 64 | object. The value that is returned from your method will be 65 | serialized. If your method returns undefined, then the member will 66 | be excluded from the serialization. 67 | 68 | If the replacer parameter is an array of strings, then it will be 69 | used to select the members to be serialized. It filters the results 70 | such that only members with keys listed in the replacer array are 71 | stringified. 72 | 73 | Values that do not have JSON representations, such as undefined or 74 | functions, will not be serialized. Such values in objects will be 75 | dropped; in arrays they will be replaced with null. You can use 76 | a replacer function to replace those with JSON values. 77 | JSON.stringify(undefined) returns undefined. 78 | 79 | The optional space parameter produces a stringification of the 80 | value that is filled with line breaks and indentation to make it 81 | easier to read. 82 | 83 | If the space parameter is a non-empty string, then that string will 84 | be used for indentation. If the space parameter is a number, then 85 | the indentation will be that many spaces. 86 | 87 | Example: 88 | 89 | text = JSON.stringify(['e', {pluribus: 'unum'}]); 90 | // text is '["e",{"pluribus":"unum"}]' 91 | 92 | 93 | text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); 94 | // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' 95 | 96 | text = JSON.stringify([new Date()], function (key, value) { 97 | return this[key] instanceof Date ? 98 | 'Date(' + this[key] + ')' : value; 99 | }); 100 | // text is '["Date(---current time---)"]' 101 | 102 | 103 | JSON.parse(text, reviver) 104 | This method parses a JSON text to produce an object or array. 105 | It can throw a SyntaxError exception. 106 | 107 | The optional reviver parameter is a function that can filter and 108 | transform the results. It receives each of the keys and values, 109 | and its return value is used instead of the original value. 110 | If it returns what it received, then the structure is not modified. 111 | If it returns undefined then the member is deleted. 112 | 113 | Example: 114 | 115 | // Parse the text. Values that look like ISO date strings will 116 | // be converted to Date objects. 117 | 118 | myData = JSON.parse(text, function (key, value) { 119 | var a; 120 | if (typeof value === 'string') { 121 | a = 122 | /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); 123 | if (a) { 124 | return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], 125 | +a[5], +a[6])); 126 | } 127 | } 128 | return value; 129 | }); 130 | 131 | myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { 132 | var d; 133 | if (typeof value === 'string' && 134 | value.slice(0, 5) === 'Date(' && 135 | value.slice(-1) === ')') { 136 | d = new Date(value.slice(5, -1)); 137 | if (d) { 138 | return d; 139 | } 140 | } 141 | return value; 142 | }); 143 | 144 | 145 | This is a reference implementation. You are free to copy, modify, or 146 | redistribute. 147 | */ 148 | 149 | /*jslint evil: true, strict: false */ 150 | 151 | /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, 152 | call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, 153 | getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, 154 | lastIndex, length, parse, prototype, push, replace, slice, stringify, 155 | test, toJSON, toString, valueOf 156 | */ 157 | 158 | 159 | // Create a JSON object only if one does not already exist. We create the 160 | // methods in a closure to avoid creating global variables. 161 | 162 | if (!this.JSON) { 163 | this.JSON = {}; 164 | } 165 | 166 | (function () { 167 | 168 | function f(n) { 169 | // Format integers to have at least two digits. 170 | return n < 10 ? '0' + n : n; 171 | } 172 | 173 | if (typeof Date.prototype.toJSON !== 'function') { 174 | 175 | Date.prototype.toJSON = function (key) { 176 | 177 | return isFinite(this.valueOf()) ? 178 | this.getUTCFullYear() + '-' + 179 | f(this.getUTCMonth() + 1) + '-' + 180 | f(this.getUTCDate()) + 'T' + 181 | f(this.getUTCHours()) + ':' + 182 | f(this.getUTCMinutes()) + ':' + 183 | f(this.getUTCSeconds()) + 'Z' : null; 184 | }; 185 | 186 | String.prototype.toJSON = 187 | Number.prototype.toJSON = 188 | Boolean.prototype.toJSON = function (key) { 189 | return this.valueOf(); 190 | }; 191 | } 192 | 193 | var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 194 | escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 195 | gap, 196 | indent, 197 | meta = { // table of character substitutions 198 | '\b': '\\b', 199 | '\t': '\\t', 200 | '\n': '\\n', 201 | '\f': '\\f', 202 | '\r': '\\r', 203 | '"' : '\\"', 204 | '\\': '\\\\' 205 | }, 206 | rep; 207 | 208 | 209 | function quote(string) { 210 | 211 | // If the string contains no control characters, no quote characters, and no 212 | // backslash characters, then we can safely slap some quotes around it. 213 | // Otherwise we must also replace the offending characters with safe escape 214 | // sequences. 215 | 216 | escapable.lastIndex = 0; 217 | return escapable.test(string) ? 218 | '"' + string.replace(escapable, function (a) { 219 | var c = meta[a]; 220 | return typeof c === 'string' ? c : 221 | '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 222 | }) + '"' : 223 | '"' + string + '"'; 224 | } 225 | 226 | 227 | function str(key, holder) { 228 | 229 | // Produce a string from holder[key]. 230 | 231 | var i, // The loop counter. 232 | k, // The member key. 233 | v, // The member value. 234 | length, 235 | mind = gap, 236 | partial, 237 | value = holder[key]; 238 | 239 | // If the value has a toJSON method, call it to obtain a replacement value. 240 | 241 | if (value && typeof value === 'object' && 242 | typeof value.toJSON === 'function') { 243 | value = value.toJSON(key); 244 | } 245 | 246 | // If we were called with a replacer function, then call the replacer to 247 | // obtain a replacement value. 248 | 249 | if (typeof rep === 'function') { 250 | value = rep.call(holder, key, value); 251 | } 252 | 253 | // What happens next depends on the value's type. 254 | 255 | switch (typeof value) { 256 | case 'string': 257 | return quote(value); 258 | 259 | case 'number': 260 | 261 | // JSON numbers must be finite. Encode non-finite numbers as null. 262 | 263 | return isFinite(value) ? String(value) : 'null'; 264 | 265 | case 'boolean': 266 | case 'null': 267 | 268 | // If the value is a boolean or null, convert it to a string. Note: 269 | // typeof null does not produce 'null'. The case is included here in 270 | // the remote chance that this gets fixed someday. 271 | 272 | return String(value); 273 | 274 | // If the type is 'object', we might be dealing with an object or an array or 275 | // null. 276 | 277 | case 'object': 278 | 279 | // Due to a specification blunder in ECMAScript, typeof null is 'object', 280 | // so watch out for that case. 281 | 282 | if (!value) { 283 | return 'null'; 284 | } 285 | 286 | // Make an array to hold the partial results of stringifying this object value. 287 | 288 | gap += indent; 289 | partial = []; 290 | 291 | // Is the value an array? 292 | 293 | if (Object.prototype.toString.apply(value) === '[object Array]') { 294 | 295 | // The value is an array. Stringify every element. Use null as a placeholder 296 | // for non-JSON values. 297 | 298 | length = value.length; 299 | for (i = 0; i < length; i += 1) { 300 | partial[i] = str(i, value) || 'null'; 301 | } 302 | 303 | // Join all of the elements together, separated with commas, and wrap them in 304 | // brackets. 305 | 306 | v = partial.length === 0 ? '[]' : 307 | gap ? '[\n' + gap + 308 | partial.join(',\n' + gap) + '\n' + 309 | mind + ']' : 310 | '[' + partial.join(',') + ']'; 311 | gap = mind; 312 | return v; 313 | } 314 | 315 | // If the replacer is an array, use it to select the members to be stringified. 316 | 317 | if (rep && typeof rep === 'object') { 318 | length = rep.length; 319 | for (i = 0; i < length; i += 1) { 320 | k = rep[i]; 321 | if (typeof k === 'string') { 322 | v = str(k, value); 323 | if (v) { 324 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 325 | } 326 | } 327 | } 328 | } else { 329 | 330 | // Otherwise, iterate through all of the keys in the object. 331 | 332 | for (k in value) { 333 | if (Object.hasOwnProperty.call(value, k)) { 334 | v = str(k, value); 335 | if (v) { 336 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 337 | } 338 | } 339 | } 340 | } 341 | 342 | // Join all of the member texts together, separated with commas, 343 | // and wrap them in braces. 344 | 345 | v = partial.length === 0 ? '{}' : 346 | gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + 347 | mind + '}' : '{' + partial.join(',') + '}'; 348 | gap = mind; 349 | return v; 350 | } 351 | } 352 | 353 | // If the JSON object does not yet have a stringify method, give it one. 354 | 355 | if (typeof JSON.stringify !== 'function') { 356 | JSON.stringify = function (value, replacer, space) { 357 | 358 | // The stringify method takes a value and an optional replacer, and an optional 359 | // space parameter, and returns a JSON text. The replacer can be a function 360 | // that can replace values, or an array of strings that will select the keys. 361 | // A default replacer method can be provided. Use of the space parameter can 362 | // produce text that is more easily readable. 363 | 364 | var i; 365 | gap = ''; 366 | indent = ''; 367 | 368 | // If the space parameter is a number, make an indent string containing that 369 | // many spaces. 370 | 371 | if (typeof space === 'number') { 372 | for (i = 0; i < space; i += 1) { 373 | indent += ' '; 374 | } 375 | 376 | // If the space parameter is a string, it will be used as the indent string. 377 | 378 | } else if (typeof space === 'string') { 379 | indent = space; 380 | } 381 | 382 | // If there is a replacer, it must be a function or an array. 383 | // Otherwise, throw an error. 384 | 385 | rep = replacer; 386 | if (replacer && typeof replacer !== 'function' && 387 | (typeof replacer !== 'object' || 388 | typeof replacer.length !== 'number')) { 389 | throw new Error('JSON.stringify'); 390 | } 391 | 392 | // Make a fake root object containing our value under the key of ''. 393 | // Return the result of stringifying the value. 394 | 395 | return str('', {'': value}); 396 | }; 397 | } 398 | 399 | 400 | // If the JSON object does not yet have a parse method, give it one. 401 | 402 | if (typeof JSON.parse !== 'function') { 403 | JSON.parse = function (text, reviver) { 404 | 405 | // The parse method takes a text and an optional reviver function, and returns 406 | // a JavaScript value if the text is a valid JSON text. 407 | 408 | var j; 409 | 410 | function walk(holder, key) { 411 | 412 | // The walk method is used to recursively walk the resulting structure so 413 | // that modifications can be made. 414 | 415 | var k, v, value = holder[key]; 416 | if (value && typeof value === 'object') { 417 | for (k in value) { 418 | if (Object.hasOwnProperty.call(value, k)) { 419 | v = walk(value, k); 420 | if (v !== undefined) { 421 | value[k] = v; 422 | } else { 423 | delete value[k]; 424 | } 425 | } 426 | } 427 | } 428 | return reviver.call(holder, key, value); 429 | } 430 | 431 | 432 | // Parsing happens in four stages. In the first stage, we replace certain 433 | // Unicode characters with escape sequences. JavaScript handles many characters 434 | // incorrectly, either silently deleting them, or treating them as line endings. 435 | 436 | cx.lastIndex = 0; 437 | if (cx.test(text)) { 438 | text = text.replace(cx, function (a) { 439 | return '\\u' + 440 | ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 441 | }); 442 | } 443 | 444 | // In the second stage, we run the text against regular expressions that look 445 | // for non-JSON patterns. We are especially concerned with '()' and 'new' 446 | // because they can cause invocation, and '=' because it can cause mutation. 447 | // But just to be safe, we want to reject all unexpected forms. 448 | 449 | // We split the second stage into 4 regexp operations in order to work around 450 | // crippling inefficiencies in IE's and Safari's regexp engines. First we 451 | // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we 452 | // replace all simple value tokens with ']' characters. Third, we delete all 453 | // open brackets that follow a colon or comma or that begin the text. Finally, 454 | // we look to see that the remaining characters are only whitespace or ']' or 455 | // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. 456 | 457 | if (/^[\],:{}\s]*$/. 458 | test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'). 459 | replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'). 460 | replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { 461 | 462 | // In the third stage we use the eval function to compile the text into a 463 | // JavaScript structure. The '{' operator is subject to a syntactic ambiguity 464 | // in JavaScript: it can begin a block or an object literal. We wrap the text 465 | // in parens to eliminate the ambiguity. 466 | 467 | j = eval('(' + text + ')'); 468 | 469 | // In the optional fourth stage, we recursively walk the new structure, passing 470 | // each name/value pair to a reviver function for possible transformation. 471 | 472 | return typeof reviver === 'function' ? 473 | walk({'': j}, '') : j; 474 | } 475 | 476 | // If the text is not JSON parseable, then a SyntaxError is thrown. 477 | 478 | throw new SyntaxError('JSON.parse'); 479 | }; 480 | } 481 | }()); 482 | -------------------------------------------------------------------------------- /qunit.css: -------------------------------------------------------------------------------- 1 | 2 | ol#qunit-tests { 3 | font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; 4 | margin:0; 5 | padding:0; 6 | list-style-position:inside; 7 | 8 | font-size: smaller; 9 | } 10 | ol#qunit-tests li{ 11 | padding:0.4em 0.5em 0.4em 2.5em; 12 | border-bottom:1px solid #fff; 13 | font-size:small; 14 | list-style-position:inside; 15 | } 16 | ol#qunit-tests li ol{ 17 | box-shadow: inset 0px 2px 13px #999; 18 | -moz-box-shadow: inset 0px 2px 13px #999; 19 | -webkit-box-shadow: inset 0px 2px 13px #999; 20 | margin-top:0.5em; 21 | margin-left:0; 22 | padding:0.5em; 23 | background-color:#fff; 24 | border-radius:15px; 25 | -moz-border-radius: 15px; 26 | -webkit-border-radius: 15px; 27 | } 28 | ol#qunit-tests li li{ 29 | border-bottom:none; 30 | margin:0.5em; 31 | background-color:#fff; 32 | list-style-position: inside; 33 | padding:0.4em 0.5em 0.4em 0.5em; 34 | } 35 | 36 | ol#qunit-tests li li.pass{ 37 | border-left:26px solid #C6E746; 38 | background-color:#fff; 39 | color:#5E740B; 40 | } 41 | ol#qunit-tests li li.fail{ 42 | border-left:26px solid #EE5757; 43 | background-color:#fff; 44 | color:#710909; 45 | } 46 | ol#qunit-tests li.pass{ 47 | background-color:#D2E0E6; 48 | color:#528CE0; 49 | } 50 | ol#qunit-tests li.fail{ 51 | background-color:#EE5757; 52 | color:#000; 53 | } 54 | ol#qunit-tests li strong { 55 | cursor:pointer; 56 | } 57 | h1#qunit-header{ 58 | background-color:#0d3349; 59 | margin:0; 60 | padding:0.5em 0 0.5em 1em; 61 | color:#fff; 62 | font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; 63 | border-top-right-radius:15px; 64 | border-top-left-radius:15px; 65 | -moz-border-radius-topright:15px; 66 | -moz-border-radius-topleft:15px; 67 | -webkit-border-top-right-radius:15px; 68 | -webkit-border-top-left-radius:15px; 69 | text-shadow: rgba(0, 0, 0, 0.5) 4px 4px 1px; 70 | } 71 | h2#qunit-banner{ 72 | font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; 73 | height:5px; 74 | margin:0; 75 | padding:0; 76 | } 77 | h2#qunit-banner.qunit-pass{ 78 | background-color:#C6E746; 79 | } 80 | h2#qunit-banner.qunit-fail, #qunit-testrunner-toolbar { 81 | background-color:#EE5757; 82 | } 83 | #qunit-testrunner-toolbar { 84 | font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; 85 | padding:0; 86 | /*width:80%;*/ 87 | padding:0em 0 0.5em 2em; 88 | font-size: small; 89 | } 90 | h2#qunit-userAgent { 91 | font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; 92 | background-color:#2b81af; 93 | margin:0; 94 | padding:0; 95 | color:#fff; 96 | font-size: small; 97 | padding:0.5em 0 0.5em 2.5em; 98 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 99 | } 100 | p#qunit-testresult{ 101 | font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; 102 | margin:0; 103 | font-size: small; 104 | color:#2b81af; 105 | border-bottom-right-radius:15px; 106 | border-bottom-left-radius:15px; 107 | -moz-border-radius-bottomright:15px; 108 | -moz-border-radius-bottomleft:15px; 109 | -webkit-border-bottom-right-radius:15px; 110 | -webkit-border-bottom-left-radius:15px; 111 | background-color:#D2E0E6; 112 | padding:0.5em 0.5em 0.5em 2.5em; 113 | } 114 | strong b.fail{ 115 | color:#710909; 116 | } 117 | strong b.pass{ 118 | color:#5E740B; 119 | } 120 | -------------------------------------------------------------------------------- /qunit.js: -------------------------------------------------------------------------------- 1 | /* 2 | * QUnit - A JavaScript Unit Testing Framework 3 | * 4 | * http://docs.jquery.com/QUnit 5 | * 6 | * Copyright (c) 2009 John Resig, Jörn Zaefferer 7 | * Dual licensed under the MIT (MIT-LICENSE.txt) 8 | * and GPL (GPL-LICENSE.txt) licenses. 9 | */ 10 | 11 | (function(window) { 12 | 13 | var QUnit = { 14 | 15 | // Initialize the configuration options 16 | init: function() { 17 | config = { 18 | stats: { all: 0, bad: 0 }, 19 | moduleStats: { all: 0, bad: 0 }, 20 | started: +new Date, 21 | blocking: false, 22 | autorun: false, 23 | assertions: [], 24 | filters: [], 25 | queue: [] 26 | }; 27 | 28 | var tests = id("qunit-tests"), 29 | banner = id("qunit-banner"), 30 | result = id("qunit-testresult"); 31 | 32 | if ( tests ) { 33 | tests.innerHTML = ""; 34 | } 35 | 36 | if ( banner ) { 37 | banner.className = ""; 38 | } 39 | 40 | if ( result ) { 41 | result.parentNode.removeChild( result ); 42 | } 43 | }, 44 | 45 | // call on start of module test to prepend name to all tests 46 | module: function(name, testEnvironment) { 47 | config.currentModule = name; 48 | 49 | synchronize(function() { 50 | if ( config.currentModule ) { 51 | QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); 52 | } 53 | 54 | config.currentModule = name; 55 | config.moduleTestEnvironment = testEnvironment; 56 | config.moduleStats = { all: 0, bad: 0 }; 57 | 58 | QUnit.moduleStart( name, testEnvironment ); 59 | }); 60 | }, 61 | 62 | asyncTest: function(testName, expected, callback) { 63 | if ( arguments.length === 2 ) { 64 | callback = expected; 65 | expected = 0; 66 | } 67 | 68 | QUnit.test(testName, expected, callback, true); 69 | }, 70 | 71 | test: function(testName, expected, callback, async) { 72 | var name = testName, testEnvironment, testEnvironmentArg; 73 | 74 | if ( arguments.length === 2 ) { 75 | callback = expected; 76 | expected = null; 77 | } 78 | // is 2nd argument a testEnvironment? 79 | if ( expected && typeof expected === 'object') { 80 | testEnvironmentArg = expected; 81 | expected = null; 82 | } 83 | 84 | if ( config.currentModule ) { 85 | name = config.currentModule + " module: " + name; 86 | } 87 | 88 | if ( !validTest(name) ) { 89 | return; 90 | } 91 | 92 | synchronize(function() { 93 | QUnit.testStart( testName ); 94 | 95 | testEnvironment = extend({ 96 | setup: function() {}, 97 | teardown: function() {} 98 | }, config.moduleTestEnvironment); 99 | if (testEnvironmentArg) { 100 | extend(testEnvironment,testEnvironmentArg); 101 | } 102 | 103 | // allow utility functions to access the current test environment 104 | QUnit.current_testEnvironment = testEnvironment; 105 | 106 | config.assertions = []; 107 | config.expected = expected; 108 | 109 | try { 110 | if ( !config.pollution ) { 111 | saveGlobal(); 112 | } 113 | 114 | testEnvironment.setup.call(testEnvironment); 115 | } catch(e) { 116 | QUnit.ok( false, "Setup failed on " + name + ": " + e.message ); 117 | } 118 | 119 | if ( async ) { 120 | QUnit.stop(); 121 | } 122 | 123 | try { 124 | callback.call(testEnvironment); 125 | } catch(e) { 126 | fail("Test " + name + " died, exception and test follows", e, callback); 127 | QUnit.ok( false, "Died on test #" + (config.assertions.length + 1) + ": " + e.message ); 128 | // else next test will carry the responsibility 129 | saveGlobal(); 130 | 131 | // Restart the tests if they're blocking 132 | if ( config.blocking ) { 133 | start(); 134 | } 135 | } 136 | }); 137 | 138 | synchronize(function() { 139 | try { 140 | checkPollution(); 141 | testEnvironment.teardown.call(testEnvironment); 142 | } catch(e) { 143 | QUnit.ok( false, "Teardown failed on " + name + ": " + e.message ); 144 | } 145 | 146 | try { 147 | QUnit.reset(); 148 | } catch(e) { 149 | fail("reset() failed, following Test " + name + ", exception and reset fn follows", e, reset); 150 | } 151 | 152 | if ( config.expected && config.expected != config.assertions.length ) { 153 | QUnit.ok( false, "Expected " + config.expected + " assertions, but " + config.assertions.length + " were run" ); 154 | } 155 | 156 | var good = 0, bad = 0, 157 | tests = id("qunit-tests"); 158 | 159 | config.stats.all += config.assertions.length; 160 | config.moduleStats.all += config.assertions.length; 161 | 162 | if ( tests ) { 163 | var ol = document.createElement("ol"); 164 | ol.style.display = "none"; 165 | 166 | for ( var i = 0; i < config.assertions.length; i++ ) { 167 | var assertion = config.assertions[i]; 168 | 169 | var li = document.createElement("li"); 170 | li.className = assertion.result ? "pass" : "fail"; 171 | li.appendChild(document.createTextNode(assertion.message || "(no message)")); 172 | ol.appendChild( li ); 173 | 174 | if ( assertion.result ) { 175 | good++; 176 | } else { 177 | bad++; 178 | config.stats.bad++; 179 | config.moduleStats.bad++; 180 | } 181 | } 182 | 183 | var b = document.createElement("strong"); 184 | b.innerHTML = name + " (" + bad + ", " + good + ", " + config.assertions.length + ")"; 185 | 186 | addEvent(b, "click", function() { 187 | var next = b.nextSibling, display = next.style.display; 188 | next.style.display = display === "none" ? "block" : "none"; 189 | }); 190 | 191 | addEvent(b, "dblclick", function(e) { 192 | var target = e && e.target ? e.target : window.event.srcElement; 193 | if ( target.nodeName.toLowerCase() === "strong" ) { 194 | var text = "", node = target.firstChild; 195 | 196 | while ( node.nodeType === 3 ) { 197 | text += node.nodeValue; 198 | node = node.nextSibling; 199 | } 200 | 201 | text = text.replace(/(^\s*|\s*$)/g, ""); 202 | 203 | if ( window.location ) { 204 | window.location.href = window.location.href.match(/^(.+?)(\?.*)?$/)[1] + "?" + encodeURIComponent(text); 205 | } 206 | } 207 | }); 208 | 209 | var li = document.createElement("li"); 210 | li.className = bad ? "fail" : "pass"; 211 | li.appendChild( b ); 212 | li.appendChild( ol ); 213 | tests.appendChild( li ); 214 | 215 | if ( bad ) { 216 | var toolbar = id("qunit-testrunner-toolbar"); 217 | if ( toolbar ) { 218 | toolbar.style.display = "block"; 219 | id("qunit-filter-pass").disabled = null; 220 | id("qunit-filter-missing").disabled = null; 221 | } 222 | } 223 | 224 | } else { 225 | for ( var i = 0; i < config.assertions.length; i++ ) { 226 | if ( !config.assertions[i].result ) { 227 | bad++; 228 | config.stats.bad++; 229 | config.moduleStats.bad++; 230 | } 231 | } 232 | } 233 | 234 | QUnit.testDone( testName, bad, config.assertions.length ); 235 | 236 | if ( !window.setTimeout && !config.queue.length ) { 237 | done(); 238 | } 239 | }); 240 | 241 | if ( window.setTimeout && !config.doneTimer ) { 242 | config.doneTimer = window.setTimeout(function(){ 243 | if ( !config.queue.length ) { 244 | done(); 245 | } else { 246 | synchronize( done ); 247 | } 248 | }, 13); 249 | } 250 | }, 251 | 252 | /** 253 | * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. 254 | */ 255 | expect: function(asserts) { 256 | config.expected = asserts; 257 | }, 258 | 259 | /** 260 | * Asserts true. 261 | * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); 262 | */ 263 | ok: function(a, msg) { 264 | QUnit.log(a, msg); 265 | 266 | config.assertions.push({ 267 | result: !!a, 268 | message: msg 269 | }); 270 | }, 271 | 272 | /** 273 | * Checks that the first two arguments are equal, with an optional message. 274 | * Prints out both actual and expected values. 275 | * 276 | * Prefered to ok( actual == expected, message ) 277 | * 278 | * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." ); 279 | * 280 | * @param Object actual 281 | * @param Object expected 282 | * @param String message (optional) 283 | */ 284 | equal: function(actual, expected, message) { 285 | push(expected == actual, actual, expected, message); 286 | }, 287 | 288 | notEqual: function(actual, expected, message) { 289 | push(expected != actual, actual, expected, message); 290 | }, 291 | 292 | deepEqual: function(a, b, message) { 293 | push(QUnit.equiv(a, b), a, b, message); 294 | }, 295 | 296 | notDeepEqual: function(a, b, message) { 297 | push(!QUnit.equiv(a, b), a, b, message); 298 | }, 299 | 300 | strictEqual: function(actual, expected, message) { 301 | push(expected === actual, actual, expected, message); 302 | }, 303 | 304 | notStrictEqual: function(actual, expected, message) { 305 | push(expected !== actual, actual, expected, message); 306 | }, 307 | 308 | start: function() { 309 | // A slight delay, to avoid any current callbacks 310 | if ( window.setTimeout ) { 311 | window.setTimeout(function() { 312 | if ( config.timeout ) { 313 | clearTimeout(config.timeout); 314 | } 315 | 316 | config.blocking = false; 317 | process(); 318 | }, 13); 319 | } else { 320 | config.blocking = false; 321 | process(); 322 | } 323 | }, 324 | 325 | stop: function(timeout) { 326 | config.blocking = true; 327 | 328 | if ( timeout && window.setTimeout ) { 329 | config.timeout = window.setTimeout(function() { 330 | QUnit.ok( false, "Test timed out" ); 331 | QUnit.start(); 332 | }, timeout); 333 | } 334 | }, 335 | 336 | /** 337 | * Resets the test setup. Useful for tests that modify the DOM. 338 | */ 339 | reset: function() { 340 | if ( window.jQuery ) { 341 | jQuery("#main").html( config.fixture ); 342 | jQuery.event.global = {}; 343 | jQuery.ajaxSettings = extend({}, config.ajaxSettings); 344 | } 345 | }, 346 | 347 | /** 348 | * Trigger an event on an element. 349 | * 350 | * @example triggerEvent( document.body, "click" ); 351 | * 352 | * @param DOMElement elem 353 | * @param String type 354 | */ 355 | triggerEvent: function( elem, type, event ) { 356 | if ( document.createEvent ) { 357 | event = document.createEvent("MouseEvents"); 358 | event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, 359 | 0, 0, 0, 0, 0, false, false, false, false, 0, null); 360 | elem.dispatchEvent( event ); 361 | 362 | } else if ( elem.fireEvent ) { 363 | elem.fireEvent("on"+type); 364 | } 365 | }, 366 | 367 | // Safe object type checking 368 | is: function( type, obj ) { 369 | return Object.prototype.toString.call( obj ) === "[object "+ type +"]"; 370 | }, 371 | 372 | // Logging callbacks 373 | done: function(failures, total) {}, 374 | log: function(result, message) {}, 375 | testStart: function(name) {}, 376 | testDone: function(name, failures, total) {}, 377 | moduleStart: function(name, testEnvironment) {}, 378 | moduleDone: function(name, failures, total) {} 379 | }; 380 | 381 | // Backwards compatibility, deprecated 382 | QUnit.equals = QUnit.equal; 383 | QUnit.same = QUnit.deepEqual; 384 | 385 | // Maintain internal state 386 | var config = { 387 | // The queue of tests to run 388 | queue: [], 389 | 390 | // block until document ready 391 | blocking: true 392 | }; 393 | 394 | // Load paramaters 395 | (function() { 396 | var location = window.location || { search: "", protocol: "file:" }, 397 | GETParams = location.search.slice(1).split('&'); 398 | 399 | for ( var i = 0; i < GETParams.length; i++ ) { 400 | GETParams[i] = decodeURIComponent( GETParams[i] ); 401 | if ( GETParams[i] === "noglobals" ) { 402 | GETParams.splice( i, 1 ); 403 | i--; 404 | config.noglobals = true; 405 | } else if ( GETParams[i].search('=') > -1 ) { 406 | GETParams.splice( i, 1 ); 407 | i--; 408 | } 409 | } 410 | 411 | // restrict modules/tests by get parameters 412 | config.filters = GETParams; 413 | 414 | // Figure out if we're running the tests from a server or not 415 | QUnit.isLocal = !!(location.protocol === 'file:'); 416 | })(); 417 | 418 | // Expose the API as global variables, unless an 'exports' 419 | // object exists, in that case we assume we're in CommonJS 420 | if ( typeof exports === "undefined" || typeof require === "undefined" ) { 421 | extend(window, QUnit); 422 | window.QUnit = QUnit; 423 | } else { 424 | extend(exports, QUnit); 425 | exports.QUnit = QUnit; 426 | } 427 | 428 | if ( typeof document === "undefined" || document.readyState === "complete" ) { 429 | config.autorun = true; 430 | } 431 | 432 | addEvent(window, "load", function() { 433 | // Initialize the config, saving the execution queue 434 | var oldconfig = extend({}, config); 435 | QUnit.init(); 436 | extend(config, oldconfig); 437 | 438 | config.blocking = false; 439 | 440 | var userAgent = id("qunit-userAgent"); 441 | if ( userAgent ) { 442 | userAgent.innerHTML = navigator.userAgent; 443 | } 444 | 445 | var toolbar = id("qunit-testrunner-toolbar"); 446 | if ( toolbar ) { 447 | toolbar.style.display = "none"; 448 | 449 | var filter = document.createElement("input"); 450 | filter.type = "checkbox"; 451 | filter.id = "qunit-filter-pass"; 452 | filter.disabled = true; 453 | addEvent( filter, "click", function() { 454 | var li = document.getElementsByTagName("li"); 455 | for ( var i = 0; i < li.length; i++ ) { 456 | if ( li[i].className.indexOf("pass") > -1 ) { 457 | li[i].style.display = filter.checked ? "none" : ""; 458 | } 459 | } 460 | }); 461 | toolbar.appendChild( filter ); 462 | 463 | var label = document.createElement("label"); 464 | label.setAttribute("for", "qunit-filter-pass"); 465 | label.innerHTML = "Hide passed tests"; 466 | toolbar.appendChild( label ); 467 | 468 | var missing = document.createElement("input"); 469 | missing.type = "checkbox"; 470 | missing.id = "qunit-filter-missing"; 471 | missing.disabled = true; 472 | addEvent( missing, "click", function() { 473 | var li = document.getElementsByTagName("li"); 474 | for ( var i = 0; i < li.length; i++ ) { 475 | if ( li[i].className.indexOf("fail") > -1 && li[i].innerHTML.indexOf('missing test - untested code is broken code') > - 1 ) { 476 | li[i].parentNode.parentNode.style.display = missing.checked ? "none" : "block"; 477 | } 478 | } 479 | }); 480 | toolbar.appendChild( missing ); 481 | 482 | label = document.createElement("label"); 483 | label.setAttribute("for", "qunit-filter-missing"); 484 | label.innerHTML = "Hide missing tests (untested code is broken code)"; 485 | toolbar.appendChild( label ); 486 | } 487 | 488 | var main = id('main'); 489 | if ( main ) { 490 | config.fixture = main.innerHTML; 491 | } 492 | 493 | if ( window.jQuery ) { 494 | config.ajaxSettings = window.jQuery.ajaxSettings; 495 | } 496 | 497 | QUnit.start(); 498 | }); 499 | 500 | function done() { 501 | if ( config.doneTimer && window.clearTimeout ) { 502 | window.clearTimeout( config.doneTimer ); 503 | config.doneTimer = null; 504 | } 505 | 506 | if ( config.queue.length ) { 507 | config.doneTimer = window.setTimeout(function(){ 508 | if ( !config.queue.length ) { 509 | done(); 510 | } else { 511 | synchronize( done ); 512 | } 513 | }, 13); 514 | 515 | return; 516 | } 517 | 518 | config.autorun = true; 519 | 520 | // Log the last module results 521 | if ( config.currentModule ) { 522 | QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); 523 | } 524 | 525 | var banner = id("qunit-banner"), 526 | tests = id("qunit-tests"), 527 | html = ['Tests completed in ', 528 | +new Date - config.started, ' milliseconds.
', 529 | '', config.stats.all - config.stats.bad, ' tests of ', config.stats.all, ' passed, ', config.stats.bad,' failed.'].join(''); 530 | 531 | if ( banner ) { 532 | banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); 533 | } 534 | 535 | if ( tests ) { 536 | var result = id("qunit-testresult"); 537 | 538 | if ( !result ) { 539 | result = document.createElement("p"); 540 | result.id = "qunit-testresult"; 541 | result.className = "result"; 542 | tests.parentNode.insertBefore( result, tests.nextSibling ); 543 | } 544 | 545 | result.innerHTML = html; 546 | } 547 | 548 | QUnit.done( config.stats.bad, config.stats.all ); 549 | } 550 | 551 | function validTest( name ) { 552 | var i = config.filters.length, 553 | run = false; 554 | 555 | if ( !i ) { 556 | return true; 557 | } 558 | 559 | while ( i-- ) { 560 | var filter = config.filters[i], 561 | not = filter.charAt(0) == '!'; 562 | 563 | if ( not ) { 564 | filter = filter.slice(1); 565 | } 566 | 567 | if ( name.indexOf(filter) !== -1 ) { 568 | return !not; 569 | } 570 | 571 | if ( not ) { 572 | run = true; 573 | } 574 | } 575 | 576 | return run; 577 | } 578 | 579 | function push(result, actual, expected, message) { 580 | message = message || (result ? "okay" : "failed"); 581 | QUnit.ok( result, result ? message + ": " + expected : message + ", expected: " + QUnit.jsDump.parse(expected) + " result: " + QUnit.jsDump.parse(actual) ); 582 | } 583 | 584 | function synchronize( callback ) { 585 | config.queue.push( callback ); 586 | 587 | if ( config.autorun && !config.blocking ) { 588 | process(); 589 | } 590 | } 591 | 592 | function process() { 593 | while ( config.queue.length && !config.blocking ) { 594 | config.queue.shift()(); 595 | } 596 | } 597 | 598 | function saveGlobal() { 599 | config.pollution = []; 600 | 601 | if ( config.noglobals ) { 602 | for ( var key in window ) { 603 | config.pollution.push( key ); 604 | } 605 | } 606 | } 607 | 608 | function checkPollution( name ) { 609 | var old = config.pollution; 610 | saveGlobal(); 611 | 612 | var newGlobals = diff( old, config.pollution ); 613 | if ( newGlobals.length > 0 ) { 614 | ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); 615 | config.expected++; 616 | } 617 | 618 | var deletedGlobals = diff( config.pollution, old ); 619 | if ( deletedGlobals.length > 0 ) { 620 | ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); 621 | config.expected++; 622 | } 623 | } 624 | 625 | // returns a new Array with the elements that are in a but not in b 626 | function diff( a, b ) { 627 | var result = a.slice(); 628 | for ( var i = 0; i < result.length; i++ ) { 629 | for ( var j = 0; j < b.length; j++ ) { 630 | if ( result[i] === b[j] ) { 631 | result.splice(i, 1); 632 | i--; 633 | break; 634 | } 635 | } 636 | } 637 | return result; 638 | } 639 | 640 | function fail(message, exception, callback) { 641 | if ( typeof console !== "undefined" && console.error && console.warn ) { 642 | console.error(message); 643 | console.error(exception); 644 | console.warn(callback.toString()); 645 | 646 | } else if ( window.opera && opera.postError ) { 647 | opera.postError(message, exception, callback.toString); 648 | } 649 | } 650 | 651 | function extend(a, b) { 652 | for ( var prop in b ) { 653 | a[prop] = b[prop]; 654 | } 655 | 656 | return a; 657 | } 658 | 659 | function addEvent(elem, type, fn) { 660 | if ( elem.addEventListener ) { 661 | elem.addEventListener( type, fn, false ); 662 | } else if ( elem.attachEvent ) { 663 | elem.attachEvent( "on" + type, fn ); 664 | } else { 665 | fn(); 666 | } 667 | } 668 | 669 | function id(name) { 670 | return !!(typeof document !== "undefined" && document && document.getElementById) && 671 | document.getElementById( name ); 672 | } 673 | 674 | // Test for equality any JavaScript type. 675 | // Discussions and reference: http://philrathe.com/articles/equiv 676 | // Test suites: http://philrathe.com/tests/equiv 677 | // Author: Philippe Rathé 678 | QUnit.equiv = function () { 679 | 680 | var innerEquiv; // the real equiv function 681 | var callers = []; // stack to decide between skip/abort functions 682 | 683 | 684 | // Determine what is o. 685 | function hoozit(o) { 686 | if (QUnit.is("String", o)) { 687 | return "string"; 688 | 689 | } else if (QUnit.is("Boolean", o)) { 690 | return "boolean"; 691 | 692 | } else if (QUnit.is("Number", o)) { 693 | 694 | if (isNaN(o)) { 695 | return "nan"; 696 | } else { 697 | return "number"; 698 | } 699 | 700 | } else if (typeof o === "undefined") { 701 | return "undefined"; 702 | 703 | // consider: typeof null === object 704 | } else if (o === null) { 705 | return "null"; 706 | 707 | // consider: typeof [] === object 708 | } else if (QUnit.is( "Array", o)) { 709 | return "array"; 710 | 711 | // consider: typeof new Date() === object 712 | } else if (QUnit.is( "Date", o)) { 713 | return "date"; 714 | 715 | // consider: /./ instanceof Object; 716 | // /./ instanceof RegExp; 717 | // typeof /./ === "function"; // => false in IE and Opera, 718 | // true in FF and Safari 719 | } else if (QUnit.is( "RegExp", o)) { 720 | return "regexp"; 721 | 722 | } else if (typeof o === "object") { 723 | return "object"; 724 | 725 | } else if (QUnit.is( "Function", o)) { 726 | return "function"; 727 | } else { 728 | return undefined; 729 | } 730 | } 731 | 732 | // Call the o related callback with the given arguments. 733 | function bindCallbacks(o, callbacks, args) { 734 | var prop = hoozit(o); 735 | if (prop) { 736 | if (hoozit(callbacks[prop]) === "function") { 737 | return callbacks[prop].apply(callbacks, args); 738 | } else { 739 | return callbacks[prop]; // or undefined 740 | } 741 | } 742 | } 743 | 744 | var callbacks = function () { 745 | 746 | // for string, boolean, number and null 747 | function useStrictEquality(b, a) { 748 | if (b instanceof a.constructor || a instanceof b.constructor) { 749 | // to catch short annotaion VS 'new' annotation of a declaration 750 | // e.g. var i = 1; 751 | // var j = new Number(1); 752 | return a == b; 753 | } else { 754 | return a === b; 755 | } 756 | } 757 | 758 | return { 759 | "string": useStrictEquality, 760 | "boolean": useStrictEquality, 761 | "number": useStrictEquality, 762 | "null": useStrictEquality, 763 | "undefined": useStrictEquality, 764 | 765 | "nan": function (b) { 766 | return isNaN(b); 767 | }, 768 | 769 | "date": function (b, a) { 770 | return hoozit(b) === "date" && a.valueOf() === b.valueOf(); 771 | }, 772 | 773 | "regexp": function (b, a) { 774 | return hoozit(b) === "regexp" && 775 | a.source === b.source && // the regex itself 776 | a.global === b.global && // and its modifers (gmi) ... 777 | a.ignoreCase === b.ignoreCase && 778 | a.multiline === b.multiline; 779 | }, 780 | 781 | // - skip when the property is a method of an instance (OOP) 782 | // - abort otherwise, 783 | // initial === would have catch identical references anyway 784 | "function": function () { 785 | var caller = callers[callers.length - 1]; 786 | return caller !== Object && 787 | typeof caller !== "undefined"; 788 | }, 789 | 790 | "array": function (b, a) { 791 | var i; 792 | var len; 793 | 794 | // b could be an object literal here 795 | if ( ! (hoozit(b) === "array")) { 796 | return false; 797 | } 798 | 799 | len = a.length; 800 | if (len !== b.length) { // safe and faster 801 | return false; 802 | } 803 | for (i = 0; i < len; i++) { 804 | if ( ! innerEquiv(a[i], b[i])) { 805 | return false; 806 | } 807 | } 808 | return true; 809 | }, 810 | 811 | "object": function (b, a) { 812 | var i; 813 | var eq = true; // unless we can proove it 814 | var aProperties = [], bProperties = []; // collection of strings 815 | 816 | // comparing constructors is more strict than using instanceof 817 | if ( a.constructor !== b.constructor) { 818 | return false; 819 | } 820 | 821 | // stack constructor before traversing properties 822 | callers.push(a.constructor); 823 | 824 | for (i in a) { // be strict: don't ensures hasOwnProperty and go deep 825 | 826 | aProperties.push(i); // collect a's properties 827 | 828 | if ( ! innerEquiv(a[i], b[i])) { 829 | eq = false; 830 | } 831 | } 832 | 833 | callers.pop(); // unstack, we are done 834 | 835 | for (i in b) { 836 | bProperties.push(i); // collect b's properties 837 | } 838 | 839 | // Ensures identical properties name 840 | return eq && innerEquiv(aProperties.sort(), bProperties.sort()); 841 | } 842 | }; 843 | }(); 844 | 845 | innerEquiv = function () { // can take multiple arguments 846 | var args = Array.prototype.slice.apply(arguments); 847 | if (args.length < 2) { 848 | return true; // end transition 849 | } 850 | 851 | return (function (a, b) { 852 | if (a === b) { 853 | return true; // catch the most you can 854 | } else if (a === null || b === null || typeof a === "undefined" || typeof b === "undefined" || hoozit(a) !== hoozit(b)) { 855 | return false; // don't lose time with error prone cases 856 | } else { 857 | return bindCallbacks(a, callbacks, [b, a]); 858 | } 859 | 860 | // apply transition with (1..n) arguments 861 | })(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length -1)); 862 | }; 863 | 864 | return innerEquiv; 865 | 866 | }(); 867 | 868 | /** 869 | * jsDump 870 | * Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com 871 | * Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php) 872 | * Date: 5/15/2008 873 | * @projectDescription Advanced and extensible data dumping for Javascript. 874 | * @version 1.0.0 875 | * @author Ariel Flesler 876 | * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html} 877 | */ 878 | QUnit.jsDump = (function() { 879 | function quote( str ) { 880 | return '"' + str.toString().replace(/"/g, '\\"') + '"'; 881 | }; 882 | function literal( o ) { 883 | return o + ''; 884 | }; 885 | function join( pre, arr, post ) { 886 | var s = jsDump.separator(), 887 | base = jsDump.indent(), 888 | inner = jsDump.indent(1); 889 | if ( arr.join ) 890 | arr = arr.join( ',' + s + inner ); 891 | if ( !arr ) 892 | return pre + post; 893 | return [ pre, inner + arr, base + post ].join(s); 894 | }; 895 | function array( arr ) { 896 | var i = arr.length, ret = Array(i); 897 | this.up(); 898 | while ( i-- ) 899 | ret[i] = this.parse( arr[i] ); 900 | this.down(); 901 | return join( '[', ret, ']' ); 902 | }; 903 | 904 | var reName = /^function (\w+)/; 905 | 906 | var jsDump = { 907 | parse:function( obj, type ) { //type is used mostly internally, you can fix a (custom)type in advance 908 | var parser = this.parsers[ type || this.typeOf(obj) ]; 909 | type = typeof parser; 910 | 911 | return type == 'function' ? parser.call( this, obj ) : 912 | type == 'string' ? parser : 913 | this.parsers.error; 914 | }, 915 | typeOf:function( obj ) { 916 | var type; 917 | if ( obj === null ) { 918 | type = "null"; 919 | } else if (typeof obj === "undefined") { 920 | type = "undefined"; 921 | } else if (QUnit.is("RegExp", obj)) { 922 | type = "regexp"; 923 | } else if (QUnit.is("Date", obj)) { 924 | type = "date"; 925 | } else if (QUnit.is("Function", obj)) { 926 | type = "function"; 927 | } else if (QUnit.is("Array", obj)) { 928 | type = "array"; 929 | } else if (QUnit.is("Window", obj) || QUnit.is("global", obj)) { 930 | type = "window"; 931 | } else if (QUnit.is("HTMLDocument", obj)) { 932 | type = "document"; 933 | } else if (QUnit.is("HTMLCollection", obj) || QUnit.is("NodeList", obj)) { 934 | type = "nodelist"; 935 | } else if (/^\[object HTML/.test(Object.prototype.toString.call( obj ))) { 936 | type = "node"; 937 | } else { 938 | type = typeof obj; 939 | } 940 | return type; 941 | }, 942 | separator:function() { 943 | return this.multiline ? this.HTML ? '
' : '\n' : this.HTML ? ' ' : ' '; 944 | }, 945 | indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing 946 | if ( !this.multiline ) 947 | return ''; 948 | var chr = this.indentChar; 949 | if ( this.HTML ) 950 | chr = chr.replace(/\t/g,' ').replace(/ /g,' '); 951 | return Array( this._depth_ + (extra||0) ).join(chr); 952 | }, 953 | up:function( a ) { 954 | this._depth_ += a || 1; 955 | }, 956 | down:function( a ) { 957 | this._depth_ -= a || 1; 958 | }, 959 | setParser:function( name, parser ) { 960 | this.parsers[name] = parser; 961 | }, 962 | // The next 3 are exposed so you can use them 963 | quote:quote, 964 | literal:literal, 965 | join:join, 966 | // 967 | _depth_: 1, 968 | // This is the list of parsers, to modify them, use jsDump.setParser 969 | parsers:{ 970 | window: '[Window]', 971 | document: '[Document]', 972 | error:'[ERROR]', //when no parser is found, shouldn't happen 973 | unknown: '[Unknown]', 974 | 'null':'null', 975 | undefined:'undefined', 976 | 'function':function( fn ) { 977 | var ret = 'function', 978 | name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE 979 | if ( name ) 980 | ret += ' ' + name; 981 | ret += '('; 982 | 983 | ret = [ ret, this.parse( fn, 'functionArgs' ), '){'].join(''); 984 | return join( ret, this.parse(fn,'functionCode'), '}' ); 985 | }, 986 | array: array, 987 | nodelist: array, 988 | arguments: array, 989 | object:function( map ) { 990 | var ret = [ ]; 991 | this.up(); 992 | for ( var key in map ) 993 | ret.push( this.parse(key,'key') + ': ' + this.parse(map[key]) ); 994 | this.down(); 995 | return join( '{', ret, '}' ); 996 | }, 997 | node:function( node ) { 998 | var open = this.HTML ? '<' : '<', 999 | close = this.HTML ? '>' : '>'; 1000 | 1001 | var tag = node.nodeName.toLowerCase(), 1002 | ret = open + tag; 1003 | 1004 | for ( var a in this.DOMAttrs ) { 1005 | var val = node[this.DOMAttrs[a]]; 1006 | if ( val ) 1007 | ret += ' ' + a + '=' + this.parse( val, 'attribute' ); 1008 | } 1009 | return ret + close + open + '/' + tag + close; 1010 | }, 1011 | functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function 1012 | var l = fn.length; 1013 | if ( !l ) return ''; 1014 | 1015 | var args = Array(l); 1016 | while ( l-- ) 1017 | args[l] = String.fromCharCode(97+l);//97 is 'a' 1018 | return ' ' + args.join(', ') + ' '; 1019 | }, 1020 | key:quote, //object calls it internally, the key part of an item in a map 1021 | functionCode:'[code]', //function calls it internally, it's the content of the function 1022 | attribute:quote, //node calls it internally, it's an html attribute value 1023 | string:quote, 1024 | date:quote, 1025 | regexp:literal, //regex 1026 | number:literal, 1027 | 'boolean':literal 1028 | }, 1029 | DOMAttrs:{//attributes to dump from nodes, name=>realName 1030 | id:'id', 1031 | name:'name', 1032 | 'class':'className' 1033 | }, 1034 | HTML:true,//if true, entities are escaped ( <, >, \t, space and \n ) 1035 | indentChar:' ',//indentation unit 1036 | multiline:true //if true, items in a collection, are separated by a \n, else just a space. 1037 | }; 1038 | 1039 | return jsDump; 1040 | })(); 1041 | 1042 | })(this); 1043 | -------------------------------------------------------------------------------- /tests.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 75 | 76 | 77 | 78 |

QUnit example

79 |

80 |

81 |
    82 |
    83 |

    Test dom

    84 |
      85 |
    • 86 | 12/25/09 87 | Merry christmas! 88 |
    • 89 |
    • 90 | 01/01/10 91 | Happy new year! 92 |
    • 93 |
    94 |
    95 | 96 | --------------------------------------------------------------------------------