//eval(function(p, a, c, k, e, r) { e = function(c) { return (c < a ? '' : e(parseInt(c / a))) + ((c = c % a) > 35 ? String.fromCharCode(c + 29) : c.toString(36)) }; if (!''.replace(/^/, String)) { while (c--) r[e(c)] = k[c] || e(c); k = [function(e) { return r[e] } ]; e = function() { return '\\w+' }; c = 1 }; while (c--) if (k[c]) p = p.replace(new RegExp('\\b' + e(c) + '\\b', 'g'), k[c]); return p } ('(d($){$.14.6B=[];$.14.1e={};$.14.1e.B={};$.14.1e.3Y=[];$.14.1e.3Z={"6C":{"56":d(4,1K){4.1z=J.Q(\'1L\');4.1A=J.Q(\'1L\');7(4.1f!==\'\'){4.1h.1Z(\'1M\',4.1f+\'7R\');4.1z.1Z(\'1M\',4.1f+\'7S\');4.1A.1Z(\'1M\',4.1f+\'7T\')}4.1z.W="6D";4.1A.W="6E";4.1z.6F=4.v.1k.40;4.1A.6F=4.v.1k.41;4.1h.O(4.1z);4.1h.O(4.1A);$(4.1h).6G(4.L);$(4.1z).2u(d(){4.D-=4.T;7(4.D<0){4.D=0}1K(4)});$(4.1A).2u(d(){7(4.D+4.T<4.1F()){4.D+=4.T}1K(4)})},"3c":d(4,1K){4.1z.W=(4.D===0)?"6D":"7U";4.1A.W=(4.42()==4.1F())?"6E":"7V"}},"6H":5,"7W":{"56":d(4,1K){6 3d=J.Q(\'6I\');6 1z=J.Q(\'1N\');6 6J=J.Q(\'1N\');6 1A=J.Q(\'1N\');6 3e=J.Q(\'6I\');6 2J=J.Q(\'57\');2J.6K="6L";2J.1M="2K";2J.1q="2K";2J.W="2K";3d.6M=4.v.1k.58;3e.6M=4.v.1k.59;1z.15=4.v.1k.40;1A.15=4.v.1k.41;3d.W="3f 7X";1z.W="3f 7Y";1A.W="3f 7Z";3e.W="3f 80";4.1h.O(3d);4.1h.O(1z);4.1h.O(2J);4.1h.O(1A);4.1h.O(3e);$(3d).2u(d(){4.D=0;1K(4)});$(1z).2u(d(){4.D-=4.T;7(4.D<0){4.D=0}1K(4)});$(1A).2u(d(){7(4.D+4.T<4.1F()){4.D+=4.T}1K(4)});$(3e).2u(d(){6 2i=43((4.1F()-1)/4.T,10)+1;4.D=(2i-1)*4.T;1K(4)});$(\'1N\',4.1h).3g(\'5a\',d(){m K});$(\'1N\',4.1h).3g(\'6N\',d(){m K});4.44=6J},"3c":d(4,1K){6 2v=6O.14.1e.3Z.6H;6 5b=6P.81(2v/2);6 2i=43((4.1F()-1)/4.T,10)+1;6 3h=43(4.D/4.T,10)+1;6 45="";6 2w;6 2L;7(2i<2v){2w=1;2L=2i}u{7(3h<=5b){2w=1;2L=2v}u{7(3h>=(2i-5b)){2w=2i-2v+1;2L=2i}u{2w=3h-6P.82(2v/2)+1;2L=2w+2v-1}}}q(6 i=2w;i<=2L;i++){7(3h!=i){45+=\'<1N 5c="3f">\'+i+\'</1N>\'}u{45+=\'<1N 5c="83">\'+i+\'</1N>\';$(\'#2K\').5d(i)}}4.44.15=45;$(\'1N\',4.44).3g(\'5a\',d(){m K});$(\'1N\',4.44).3g(\'6N\',d(){m K});$(\'#2K\').3g(\'6Q\',d(){6 6R=($(\'#2K\').5d()*1)-1;4.D=6R*4.T;1K(4);m K})}}};$.14.1e.2M={"3i-1O":d(a,b){6 x=a.20();6 y=b.20();m((x<y)?-1:((x>y)?1:0))},"3i-2N":d(a,b){6 x=a.20();6 y=b.20();m((x<y)?1:((x>y)?-1:0))},"46-1O":d(a,b){6 x=a.X(/<.*?>/g,"").20();6 y=b.X(/<.*?>/g,"").20();m((x<y)?-1:((x>y)?1:0))},"46-2N":d(a,b){6 x=a.X(/<.*?>/g,"").20();6 y=b.X(/<.*?>/g,"").20();m((x<y)?1:((x>y)?-1:0))},"29-1O":d(a,b){6 x=21.1P(a);6 y=21.1P(b);7(3j(x)){x=21.1P("2j/2j/47 1G:1G:1G")}7(3j(y)){y=21.1P("2j/2j/47 1G:1G:1G")}m x-y},"29-2N":d(a,b){6 x=21.1P(a);6 y=21.1P(b);7(3j(x)){x=21.1P("2j/2j/47 1G:1G:1G")}7(3j(y)){y=21.1P("2j/2j/47 1G:1G:1G")}m y-x},"3k-1O":d(a,b){6 x=a=="-"?0:a;6 y=b=="-"?0:b;m x-y},"3k-2N":d(a,b){6 x=a=="-"?0:a;6 y=b=="-"?0:b;m y-x}};$.14.1e.3l=[d(16){6 6S="84.-";6 48;6 5e=K;q(i=0;i<16.9;i++){48=16.6T(i);7(6S.5f(48)==-1){m s}7(48=="."){7(5e){m s}5e=F}}m\'3k\'},d(16){7(!3j(21.1P(16))){m\'29\'}m s}];$.14.1e.6U={"6V":0};$.14.85=d(z){6 3m=$.14.6B;d 6W(){8.3n=d(){7(8.C.22){m 8.5g}u{m 8.17.9}};8.1F=d(){7(8.C.22){m 8.5h}u{m 8.E.9}};8.42=d(){7(8.C.22){m 8.D+8.E.9}u{m 8.1H}};8.3o=s;8.C={"2O":F,"5i":F,"3p":F,"2x":F,"49":F,"4a":F,"3q":K,"5j":F,"2P":K,"22":K};8.v={"5k":"86...","5l":"87 6X 88 89","5m":"8a 8b 5n 8c","4b":"6Y 5o 5p 5q 5r 5s 5n","4c":"6Y 0 5p 0 5r 0 8d","4d":"(8e 8f 5t 8g 5n)","2Q":"","18":"8h:","3r":"","1k":{"58":"&6Z;&6Z;","40":"","41":"","59":""}};8.o=[];8.E=[];8.17=[];8.h=[];8.5u=0;8.2k=[];8.1g={"18":"","R":F};8.1r=[];8.1Q=s;8.1R=s;8.M=[[0,\'1O\']];8.1S=s;8.2y=[\'8i\',\'8j\'];8.4e=s;8.4f=s;8.4g=s;8.4h=s;8.2R=s;8.1f="";8.L=s;8.8k=0;8.5v=K;8.23=[];8.1h=s;8.1z=s;8.1A=s;8.5w=\'8l\';8.4i="6C";8.70=60*60*2;8.2S=s;8.4j=F;8.5x=$.5y;8.T=10;8.D=0;8.1H=10;8.5g=0;8.5h=0}8.B={};8.8m=d(){4k(1l(8[0]))};8.8n=d(1u,U,R){6 4=1l(8[0]);7(w R==\'H\'){R=F}7(w U=="H"||U===s){2l(4,{"18":1u,"R":R},1)}u{4.1r[U].18=1u;4.1r[U].R=R;2l(4,4.1g,1)}};8.8o=d(2T){m 1l(8[0])};8.8p=d(Y){6 4=1l(8[0]);4.M=Y;2U(4)};8.8q=d(1T,1v){6 2a=[];6 1m;7(w 1v==\'H\'){1v=F}6 4=1l(8[0]);7(w 1T[0]=="4l"){q(6 i=0;i<1T.9;i++){1m=2b(4,1T[i]);7(1m==-1){m 2a}2a.I(1m)}}u{1m=2b(4,1T);7(1m==-1){m 2a}2a.I(1m)}4.E=4.17.1w();2m(4,1);7(1v){4k(4)}m 2a};8.8r=d(4m,5z){6 4=1l(8[0]);6 i;q(i=0;i<4.17.9;i++){7(4.17[i]==4m){4.17.1U(i,1);2z}}q(i=0;i<4.E.9;i++){7(4.E[i]==4m){4.E.1U(i,1);2z}}2m(4,1);7(w 5z=="d"){5z.71(8)}7(4.D>=4.E.9){4.D-=4.T;7(4.D<0){4.D=0}}1B(4);1x(4);m 4.o[4m].N.1w()};8.8s=d(1v){6 4=1l(8[0]);3s(4);7(w 1v==\'H\'||1v){1x(4)}};8.8t=d(P,72,1n){6 4=1l(8[0]);8.73(P);6 4n=J.Q("1o");6 3t=J.Q("1b");4n.O(3t);3t.W=1n;3t.74=3u(4);3t.15=72;$(4n).6G(P);7(!4.C.22){4.23.I({"P":4n,"2n":P})}};8.73=d(P){6 4=1l(8[0]);q(6 i=0;i<4.23.9;i++){7(4.23[i].2n==P){6 5A=4.23[i].P.1I;7(5A){5A.2c(4.23[i].P)}4.23.1U(i,1);m 0}}m 1};8.8u=d(1i){6 4=1l(8[0]);7(w 1i!=\'H\'){m 4.o[1i].N}m 2V(4)};8.8v=d(1i){6 4=1l(8[0]);7(w 1i!=\'H\'){m 4.o[1i].P}m 3v(4)};8.8w=d(2T){6 4=1l(8[0]);6 i;7(2T.75=="8x"){q(i=0;i<4.o.9;i++){7(4.o[i].P==2T){m i}}}u 7(2T.75=="8y"){q(i=0;i<4.o.9;i++){6 1C=0;q(6 j=0;j<4.h.9;j++){7(4.h[j].Z){7(4.o[i].P.1c(\'1b\')[j-1C]==2T){m[i,j-1C,j]}}u{1C++}}}}m s};8.3c=d(1T,1i,U,1v){6 4=1l(8[0]);6 2A;6 1V;7(w 1v==\'H\'){1v=F}7(w 1T!=\'4l\'){1V=1T;4.o[1i].N[U]=1V;7(4.h[U].24!==s){1V=4.h[U].24({"4o":1i,"4p":U,"1d":4.o[1i].N});7(4.h[U].2W){4.o[1i].N[U]=1V}}2A=2o(4,U);7(2A!==s){4.o[1i].P.1c(\'1b\')[2A].15=1V}}u{7(1T.9!=4.h.9){8z(\'8A: 8B 8C 8D 5p 3c 8E 8F 76 8G 8H 5r 8I 8J \'+\'76 5B 5C 8K - 5C 8 8L \'+4.h.9);m 1}q(6 i=0;i<1T.9;i++){1V=1T[i];4.o[1i].N[i]=1V;7(4.h[i].24!==s){1V=4.h[i].24({"4o":1i,"4p":i,"1d":4.o[1i].N});7(4.h[i].2W){4.o[1i].N[i]=1V}}2A=2o(4,i);7(2A!==s){4.o[1i].P.1c(\'1b\')[2A].15=1V}}}2m(4,1);7(1v){4k(4)}m 0};8.8M=d(19,3w){6 4=1l(8[0]);6 i,G;6 2d=4.h.9;6 11;7(4.h[19].Z==3w){m}6 3x=$(\'2e 1o\',4.L)[0];6 2p=$(\'5D 1o\',4.L)[0];6 3y=[];6 3z=[];q(i=0;i<2d;i++){3y.I(4.h[i].V);3z.I(4.h[i].5E)}7(3w){6 5F=0;q(i=0;i<19;i++){7(4.h[i].Z){5F++}}7(5F>=3u(4)){3x.O(3y[19]);7(2p){2p.O(3z[19])}q(i=0,G=4.o.9;i<G;i++){11=4.o[i].2B[19];4.o[i].P.O(11)}}u{6 2X;q(i=19;i<2d;i++){2X=2o(4,i);7(2X!==s){2z}}3x.3A(3y[19],3x.1c(\'1W\')[2X]);7(2p){2p.3A(3z[19],2p.1c(\'1W\')[2X])}q(i=0,G=4.o.9;i<G;i++){11=4.o[i].2B[19];4.o[i].P.3A(11,4.o[i].P.1c(\'1b\')[2X])}}4.h[19].Z=F}u{3x.2c(3y[19]);7(2p){2p.2c(3z[19])}6 77=2o(4,19);q(i=0,G=4.o.9;i<G;i++){11=4.o[i].P.1c(\'1b\')[77];4.o[i].2B[19]=11;11.1I.2c(11)}4.h[19].Z=K}};d 78(2Y){m d(){6 79=[1l(8[0])].5G(8N.8O.1w.71(8P));m $.14.1e.B[2Y].8Q(8,79)}}6 5H=K;q(6 2Y 5C $.14.1e.B){7(2Y){8[2Y]=78(2Y);5H=F}}d 2Z(4){7(4.5v===K){7a(d(){2Z(4)},8R);m}4q(4);4r(4);7(4.C.2x){2U(4,K);30(4)}u{4.E=4.17.1w();1B(4);1x(4)}7(4.2S!==s&&!4.C.22){2f(4,F);$.5y(4.2S,s,d(1p){q(6 i=0;i<1p.2q.9;i++){2b(4,1p.2q[i])}7(4.C.2x){2U(4)}u{4.E=4.17.1w();1B(4);1x(4)}2f(4,K);7(w 4.2R==\'d\'){4.2R(4)}});m}7(w 4.2R==\'d\'){4.2R(4)}}d 3B(4,v,7b){A(4.v,v,\'5k\');A(4.v,v,\'5l\');A(4.v,v,\'5m\');A(4.v,v,\'4b\');A(4.v,v,\'4c\');A(4.v,v,\'4d\');A(4.v,v,\'2Q\');A(4.v,v,\'18\');7(w v.1k!=\'H\'){A(4.v.1k,v.1k,\'58\');A(4.v.1k,v.1k,\'40\');A(4.v.1k,v.1k,\'41\');A(4.v.1k,v.1k,\'59\')}7(7b){2Z(4)}}d 4s(4,1s,V){4.h[4.h.9++]={"S":s,"4t":F,"Z":F,"5I":F,"4u":F,"2r":s,"1t":\'\',"12":s,"1n":s,"24":s,"2W":F,"1j":4.h.9-1,"V":V?V:J.Q(\'1W\'),"5E":s};6 31=4.h.9-1;7(w 1s!=\'H\'&&1s!==s){6 1D=4.h[31];7(w 1s.S!=\'H\'){1D.S=1s.S;1D.4t=K}A(1D,1s,"Z");A(1D,1s,"5I");A(1D,1s,"4u");A(1D,1s,"2r");A(1D,1s,"1t");A(1D,1s,"12");A(1D,1s,"1n");A(1D,1s,"24");A(1D,1s,"2W");A(1D,1s,"1j")}7(w 4.1r[31]==\'H\'){4.1r[31]={"18":"","R":F}}}d 2b(4,1d){7(1d.9!=4.h.9){m-1}6 26=4.o.9;4.o.I({"7c":4.5u++,"N":1d.1w(),"P":J.Q(\'1o\'),"2B":[]});6 11;q(6 i=0;i<1d.9;i++){11=J.Q(\'1b\');7(w 4.h[i].24==\'d\'){6 32=4.h[i].24({"4o":26,"4p":i,"1d":1d});11.15=32;7(4.h[i].2W){4.o[26].N[i]=32}}u{11.15=1d[i]}7(4.h[i].1n!==s){11.W=4.h[i].1n}7(4.h[i].4t&&4.h[i].S!=\'3i\'){7(4.h[i].S===s){4.h[i].S=2C(1d[i])}u 7(4.h[i].S=="29"||4.h[i].S=="3k"){4.h[i].S=2C(1d[i])}}7(4.h[i].Z){4.o[26].P.O(11)}u{4.o[26].2B[i]=11}}4.17.I(26);m 26}d 4v(4){6 8S;6 8T=$(\'33:34(0)>1o\').9;6 5J;6 i,j;7(4.2S===s){$(\'33:34(0)>1o\',4.L).5K(d(){6 26=4.o.9;4.o.I({"7c":4.5u++,"N":[],"P":8,"2B":[]});4.17.I(26);6 7d=4.o[26].N;$(\'1b\',8).5K(d(i){7d[i]=8.15})})}6 1C=0;q(i=0;i<4.h.9;i++){7(4.h[i].2r===s){4.h[i].2r=4.h[i].V.15}6 5L=4.h[i].4t;6 5M=w 4.h[i].24==\'d\';6 5N=4.h[i].1n!==s;6 Z=4.h[i].Z;7(5L||5M||5N||!Z){5J=4.o.9;q(j=0;j<5J;j++){6 35=4.o[j].P.1c(\'1b\')[i-1C];7(5L){7(4.h[i].S===s){4.h[i].S=2C(4.o[j].N[i])}u 7(4.h[i].S=="29"||4.h[i].S=="3k"){4.h[i].S=2C(4.o[j].N[i])}}7(5M){6 32=4.h[i].24({"4o":j,"4p":i,"1d":4.o[j].N});35.15=32;7(4.h[i].2W){4.o[j].N[i]=32}}7(5N){35.W+=\' \'+4.h[i].1n}7(!Z){4.o[j].2B[i]=35;35.1I.2c(35)}}7(!Z){1C++}}}}d 4r(4){6 i,V;6 7e=4.L.1c(\'2e\')[0].1c(\'1W\').9;6 1C=0;7(7e!==0){q(i=0;i<4.h.9;i++){V=4.h[i].V;7(4.h[i].Z){7(4.h[i].12!==s){V.27.36=4.h[i].12}7(4.h[i].2r!=V.15){V.15=4.h[i].2r}}u{V.1I.2c(V);1C++}}}u{6 P=J.Q("1o");q(i=0;i<4.h.9;i++){7(4.h[i].Z){V=4.h[i].V;7(4.h[i].1n!==s){V.W=4.h[i].1n}7(4.h[i].12!==s){V.27.36=4.h[i].12}V.15=4.h[i].2r;P.O(V)}}$(\'2e\',4.L).46(\'\')[0].O(P)}7(4.C.2x){q(i=0;i<4.h.9;i++){$(4.h[i].V).2u(d(e){6 2D;q(6 i=0;i<4.h.9;i++){7(4.h[i].V==8){2D=i;2z}}7(4.h[2D].4u===K){m}5O=d(){7(e.8U){6 5P=K;q(6 i=0;i<4.M.9;i++){7(4.M[i][0]==2D){7(4.M[i][1]=="1O"){4.M[i][1]="2N"}u{4.M.1U(i,1)}5P=F;2z}}7(5P===K){4.M.I([2D,"1O"])}}u{7(4.M.9==1&&4.M[0][0]==2D){4.M[0][1]=4.M[0][1]=="1O"?"2N":"1O"}u{4.M.1U(0,4.M.9);4.M.I([2D,"1O"])}}2U(4)};7(!4.C.3q){5O()}u{2f(4,F);7a(d(){5O();2f(4,K)},0)}})}$(\'2e 1W\',4.L).5a(d(){8.8V=d(){m K};m K})}7(4.C.4a){4.L.27.36=4.L.3C+"3D"}6 5Q=4.L.1c(\'5D\');7(5Q.9!==0){5R=5Q[0].1c(\'1W\');q(i=0,G=5R.9;i<G;i++){4.h[i].5E=5R[i]}}}d 1x(4){6 i;6 2s=[];6 3E=0;6 7f=4.2y.9;6 5S=4.23.9;7(4.C.22&&!4w(4)){m}7(4.E.9!==0){6 37=4.D;6 3F=4.1H;7(4.C.22){37=0;3F=4.o.9}q(6 j=37;j<3F;j++){6 2E=4.o[4.E[j]].P;$(2E).5T(4.2y.5U(\' \'));$(2E).4x(4.2y[3E%7f]);7(w 4.4e=="d"){2E=4.4e(2E,4.o[4.E[j]].N,3E,j)}2s.I(2E);3E++;7(5S!==0){q(6 k=0;k<5S;k++){7(2E==4.23[k].2n){2s.I(4.23[k].P)}}}}}u{2s[0]=J.Q(\'1o\');7(w 4.2y[0]!=\'H\'){2s[0].W=4.2y[0]}6 11=J.Q(\'1b\');11.1Z(\'8W\',"8X");11.74=4.h.9;11.W=\'8Y\';11.15=4.v.5m;2s[3E].O(11)}7(w 4.4f==\'d\'){4.4f($(\'2e 1o\',4.L)[0],2V(4),4.D,4.1H,4.E)}7(w 4.4g==\'d\'){4.4g($(\'5D 1o\',4.L)[0],2V(4),4.D,4.1H,4.E)}6 1E=$(\'33:34(0)>1o\',4.L);q(i=0;i<1E.9;i++){1E[i].1I.2c(1E[i])}6 7g=$(\'33:34(0)\',4.L);q(i=0;i<2s.9;i++){7g[0].O(2s[i])}7(4.C.2O){$.14.1e.3Z[4.4i].3c(4,d(4){1B(4);1x(4)})}7(4.C.49){7(4.1F()===0&&4.1F()==4.3n()){4.1Q.15=4.v.4c+4.v.2Q}u 7(4.1F()===0){4.1Q.15=4.v.4c+\' \'+4.v.4d.X(\'5t\',4.3n())+4.v.2Q}u 7(4.1F()==4.3n()){4.1Q.15=4.v.4b.X(\'5o\',4.D+1).X(\'5q\',4.42()).X(\'5s\',4.1F())+4.v.2Q}u{4.1Q.15=4.v.4b.X(\'5o\',4.D+1).X(\'5q\',4.42()).X(\'5s\',4.1F())+\' \'+4.v.4d.X(\'5t\',4.3n())+4.v.2Q}}7(4.C.22&&4.C.2x){30(4)}4y(4);7(w 4.4h==\'d\'){4.4h(4)}}d 4k(4){7(4.C.2x){2U(4,4.1g)}u 7(4.C.3p){2l(4,4.1g)}u{1B(4);1x(4)}}d 4w(4){7(4.4j){2f(4,F);6 2d=4.h.9;6 o=[];6 i;o.I({"1q":"2d","1a":2d});o.I({"1q":"38","1a":3G(4)});o.I({"1q":"7h","1a":4.T});o.I({"1q":"4z","1a":4.D});o.I({"1q":"18","1a":4.1g.18});o.I({"1q":"R","1a":4.1g.R});q(i=0;i<2d;i++){o.I({"1q":"8Z"+i,"1a":4.1r[i].18});o.I({"1q":"90"+i,"1a":4.1r[i].R})}6 3H=4.1S!==s?4.1S.9:0;6 5V=4.M.9;o.I({"1q":"91","1a":3H+5V});q(i=0;i<3H;i++){o.I({"1q":"7i"+i,"1a":4.1S[i][0]});o.I({"1q":"7j"+i,"1a":4.1S[i][1]})}q(i=0;i<5V;i++){o.I({"1q":"7i"+(i+3H),"1a":4.M[i][0]});o.I({"1q":"7j"+(i+3H),"1a":4.M[i][1]})}4.5x(4.2S,o,d(1p){7k(4,1p)});m K}u{m F}}d 7k(4,1p){3s(4);4.5g=1p.92;4.5h=1p.93;6 5W=3G(4);5X=(1p.38!=\'H\'&&5W!==""&&1p.38!=5W);7(5X){6 7l=4A(4,1p.38)}q(6 i=0,G=1p.2q.9;i<G;i++){7(5X){6 1d=[];q(6 j=0,28=4.h.9;j<28;j++){1d.I(1p.2q[i][7l[j]])}2b(4,1d)}u{2b(4,1p.2q[i])}}4.E=4.17.1w();4.4j=K;1x(4);4.4j=F;2f(4,K)}d 4q(4){6 4B=J.Q(\'1L\');4.L.1I.3A(4B,4.L);6 3I=J.Q(\'1L\');3I.W="94";7(4.1f!==\'\'){3I.1Z(\'1M\',4.1f+\'95\')}6 1X=3I;6 2F=4.5w.3J(\'\');q(6 i=0;i<2F.9;i++){6 2g=2F[i];7(2g==\'<\'){6 4C=J.Q(\'1L\');6 4D=2F[i+1];7(4D=="\'"||4D==\'"\'){6 1n="";6 j=2;5Y(2F[i+j]!=4D){1n+=2F[i+j];j++}4C.W=1n;i+=j}1X.O(4C);1X=4C}u 7(2g==\'>\'){1X=1X.1I}u 7(2g==\'l\'&&4.C.2O&&4.C.5i){1X.O(4E(4))}u 7(2g==\'f\'&&4.C.3p){1X.O(4F(4))}u 7(2g==\'r\'&&4.C.3q){1X.O(4G(4))}u 7(2g==\'t\'){1X.O(4.L)}u 7(2g==\'i\'&&4.C.49){1X.O(4H(4))}u 7(2g==\'p\'&&4.C.2O){1X.O(4I(4))}}4B.1I.96(3I,4B)}d 4F(4){6 39=J.Q(\'1L\');7(4.1f!==\'\'){39.1Z(\'1M\',4.1f+\'97\')}39.W="98";39.15=4.v.18+\' <57 6K="6L" 1a="\'+4.1g.18.X(\'"\',\'&99;\')+\'" />\';$("57",39).6Q(d(e){2l(4,{"18":8.1a,"R":4.1g.R})});m 39}d 4H(4){6 1Q=J.Q(\'1L\');4.1Q=1Q;7(4.1f!==\'\'){4.1Q.1Z(\'1M\',4.1f+\'9a\')}4.1Q.W="9b";m 1Q}d 4I(4){6 1h=J.Q(\'1L\');1h.W="9c";4.1h=1h;$.14.1e.3Z[4.4i].56(4,d(4){1B(4);1x(4)});m 1h}d 4E(4){6 1t=(4.1f==="")?"":\'1q="\'+4.1f+\'7m"\';6 7n=\'<4J 9d="1" \'+1t+\'>\'+\'<2h 1a="10">10</2h>\'+\'<2h 1a="25">25</2h>\'+\'<2h 1a="50">50</2h>\'+\'<2h 1a="7o">7o</2h>\'+\'</4J>\';6 2G=J.Q(\'1L\');7(4.1f!==\'\'){2G.1Z(\'1M\',4.1f+\'7m\')}2G.W="9e";2G.15=4.v.5l.X(\'6X\',7n);$(\'4J 2h[1a="\'+4.T+\'"]\',2G).9f("9g",F);$(\'4J\',2G).9h(d(e){4.T=43($(8).5d(),10);1B(4);7(4.1H==4.E.9){4.D=4.1H-4.T;7(4.D<0){4.D=0}}7(4.T==-1){4.D=0}1x(4)});m 2G}d 4G(4){6 1R=J.Q(\'1L\');4.1R=1R;7(4.1f!==\'\'){4.1R.1Z(\'1M\',4.1f+\'9i\')}4.1R.O(J.9j(4.v.5k));4.1R.W="9k";4.1R.27.5Z="61";4.L.1I.3A(4.1R,4.L);m 1R}d 2f(4,3w){7(4.C.3q){4.1R.27.5Z=3w?"9l":"61"}}d 2l(4,62,2t){4K(4,62.18,2t,62.R);q(6 i=0;i<4.1r.9;i++){4L(4,4.1r[i].18,i,4.1r[i].R)}7($.14.1e.3Y.9!=0){7p(4)}7(w 4.2H!=\'H\'&&4.2H!=-1){4.D=4.2H;4.2H=-1}u{4.D=0}1B(4);1x(4);2m(4,0)}d 7p(4){6 i,j,G,28;6 63=$.14.1e.3Y;q(6 i=0,G=63.9;i<G;i++){6 1C=0;q(6 j=0,28=4.E.9;j<28;j++){6 1d=[];6 64=4.E[j-1C];7(!63[i](4,4.o[64].N,64)){4.E.1U(j-1C,1);1C++}}}}d 4L(4,1u,U,R){7(1u===""){m}6 3K=0;6 7q=R?3L(1u):1u;6 3M=3N 65(7q,"i");q(i=4.E.9-1;i>=0;i--){6 16=3O(4.o[4.E[i]].N[U],4.h[U].S);7(!3M.66(16)){4.E.1U(i,1);3K++}}}d 4K(4,1u,2t,R){6 9m,i,j;7(w 2t==\'H\'||2t===s){2t=0}7($.14.1e.3Y.9!=0){2t=1}7(w 1v==\'H\'||1v===s){1v=F}6 7r=R?3L(1u).3J(\' \'):1u.3J(\' \');6 7s=\'^(?=.*?\'+7r.5U(\')(?=.*?\')+\').*$\';6 3M=3N 65(7s,"i");7(1u.9<=0){4.E.1U(0,4.E.9);4.E=4.17.1w()}u{7(4.E.9==4.17.9||4.1g.18.9>1u.9||2t==1||1u.5f(4.1g.18)!==0){4.E.1U(0,4.E.9);2m(4,1);q(i=0;i<4.17.9;i++){7(3M.66(4.2k[i])){4.E.I(4.17[i])}}}u{6 3K=0;q(i=0;i<4.2k.9;i++){7(!3M.66(4.2k[i])){4.E.1U(i-3K,1);3K++}}}}4.1g.18=1u;4.1g.R=R}d 2U(4,67){6 Y=[];6 2M=$.14.1e.2M;6 o=4.o;6 1j;6 3P;6 i;7(4.M.9!==0||4.1S!==s){7(4.1S!==s){Y=4.1S.5G(4.M)}u{Y=4.M.1w()}7(!68.9n){6 4M="6 7t = d(a,b){"+"6 1m;";q(i=0;i<Y.9-1;i++){1j=4.h[Y[i][0]].1j;3P=4.h[1j].S;4M+="1m = 2M[\'"+3P+"-"+Y[i][1]+"\']"+"( o[a].N["+1j+"], o[b].N["+1j+"] ); 7 ( 1m === 0 )"}1j=4.h[Y[Y.9-1][0]].1j;3P=4.h[1j].S;4M+="1m = 2M[\'"+3P+"-"+Y[Y.9-1][1]+"\']"+"( o[a].N["+1j+"], o[b].N["+1j+"] ); m 1m;}";7u(4M);4.17.7v(7t)}u{6 3Q=[];6 G=Y.9;q(i=0;i<G;i++){1j=4.h[Y[i][0]].1j;3Q.I([1j,4.h[1j].S+\'-\'+Y[i][1]])}4.17.7v(d(a,b){6 1m;q(6 i=0;i<G;i++){1m=2M[3Q[i][1]](o[a].N[3Q[i][0]],o[b].N[3Q[i][0]]);7(1m!==0){m 1m}}m 0})}}7(w 67==\'H\'||67){30(4)}7(4.C.3p){2l(4,4.1g,1)}u{4.E=4.17.1w();4.D=0;1B(4);1x(4)}}d 30(4){6 i;6 Y;6 2d=4.h.9;q(i=0;i<2d;i++){$(4.h[i].V).5T("7w 7x 7y")}7(4.1S!==s){Y=4.1S.5G(4.M)}u{Y=4.M.1w()}q(i=0;i<4.h.9;i++){7(4.h[i].4u&&4.h[i].Z){6 1n="7y";q(6 j=0;j<Y.9;j++){7(Y[j][0]==i){1n=(Y[j][1]=="1O")?"7w":"7x";2z}}$(4.h[i].V).4x(1n)}}7(4.C.5j){6 1E=3v(4);$(\'1b\',1E).5T(\'9o 9p 7z\');6 4N=1;q(i=0;i<Y.9;i++){6 2I=2o(4,Y[i][0]);7(2I!==s){7(4N<=2){$(\'1b:34(\'+2I+\')\',1E).4x(\'9q\'+4N)}u{$(\'1b:34(\'+2I+\')\',1E).4x(\'7z\')}4N++}}}}d 69(4,4O){6 U=-1;q(6 i=0;i<4.h.9;i++){7(4.h[i].Z===F){U++}7(U==4O){m i}}m s}d 2o(4,4O){6 6a=-1;q(6 i=0;i<4.h.9;i++){7(4.h[i].Z===F){6a++}7(i==4O){m 4.h[i].Z===F?6a:s}}m s}d 3u(6b){6 2I=0;q(6 i=0;i<6b.h.9;i++){7(6b.h[i].Z===F){2I++}}m 2I}d 2m(4,6c){4.2k.1U(0,4.2k.9);6 6d=(w 6c!=\'H\'&&6c==1)?4.17:4.E;q(i=0;i<6d.9;i++){4.2k[i]=\'\';q(j=0;j<4.h.9;j++){7(4.h[j].5I){6 16=4.o[6d[i]].N[j];4.2k[i]+=3O(16,4.h[j].S)+\' \'}}}}d 3O(16,S){7(S=="46"){m 16.X(/\\n/g," ").X(/<.*?>/g,"")}u 7(w 16=="3i"){m 16.X(/\\n/g," ")}m 16}d 1B(4){7(4.C.2O===K){4.1H=4.E.9}u{7(4.D+4.T>4.E.9||4.T==-1){4.1H=4.E.9}u{4.1H=4.D+4.T}}}d 4P(12,2n){7(!12||12===s||12===\'\'){m 0}7(w 2n=="H"){2n=J.1c(\'9r\')[0]}6 6e;6 3R=J.Q("1L");3R.27.36=12;2n.O(3R);6e=3R.3C;2n.2c(3R);m(6e)}d 4Q(4){6 7A=4.L.3C;6 6f=0;6 4R;6 6g=0;6 3S=4.h.9;6 i;6 6h=$(\'2e 1W\',4.L);q(i=0;i<3S;i++){7(4.h[i].Z){6g++;7(4.h[i].12!==s){4R=4P(4.h[i].12,4.L.1I);6f+=4R;4.h[i].12=4R+"3D"}}}7(3S==6h.9&&6f===0&&6g==3S){q(i=0;i<4.h.9;i++){4.h[i].12=6h[i].3C+"3D"}}u{6 1Y=4.L.9s(K);1Y.1Z("1M",\'\');6 7B=\'<5B 5c="\'+1Y.W+\'">\';6 4S="<1o>";6 3T="<1o>";q(i=0;i<3S;i++){7(4.h[i].Z){4S+=\'<1W>\'+4.h[i].2r+\'</1W>\';7(4.h[i].12!==s){6 12=\'\';7(4.h[i].12!==s){12=\' 27="36:\'+4.h[i].12+\';"\'}3T+=\'<1b\'+12+\' 6i="\'+i+\'">\'+6j(4,i)+\'</1b>\'}u{3T+=\'<1b 6i="\'+i+\'">\'+6j(4,i)+\'</1b>\'}}}4S+="</1o>";3T+="</1o>";1Y=$(7B+4S+3T+\'</5B>\')[0];1Y.27.36=7A+"3D";1Y.27.5Z="61";1Y.27.9t="9u";4.L.1I.O(1Y);6 6k=$("1b",1Y);6 6l;q(i=0;i<6k.9;i++){6l=6k[i].4T(\'6i\');4.h[6l].12=$("1b",1Y)[i].3C+"3D"}4.L.1I.2c(1Y)}}d 6j(4,19){6 6m=0;6 4U=-1;q(6 i=0;i<4.o.9;i++){7(4.o[i].N[19].9>6m){6m=4.o[i].N[19].9;4U=i}}7(4U>=0){m 4.o[4U].N[19]}m\'\'}d 6n(4V,6o){7(4V.9!=6o.9){m 1}q(6 i=0;i<4V.9;i++){7(4V[i]!=6o[i]){m 2}}m 0}d 2C(16){6 3l=$.14.1e.3l;6 G=3l.9;q(6 i=0;i<G;i++){6 S=3l[i](16);7(S!==s){m S}}m\'3i\'}d 1l(L){q(6 i=0;i<3m.9;i++){7(3m[i].L==L){m 3m[i]}}m s}d 2V(4){6 1d=[];6 G=4.o.9;q(6 i=0;i<G;i++){1d.I(4.o[i].N)}m 1d}d 3v(4){6 6p=[];6 G=4.o.9;q(6 i=0;i<G;i++){6p.I(4.o[i].P)}m 6p}d 3L(7C){6 7D=[\'/\',\'.\',\'*\',\'+\',\'?\',\'|\',\'(\',\')\',\'[\',\']\',\'{\',\'}\',\'\\\\\',\'$\',\'^\'];6 7E=3N 65(\'(\\\\\'+7D.5U(\'|\\\\\')+\')\',\'g\');m 7C.X(7E,\'\\\\$1\')}d 4A(4,38){6 7F=38.3J(\',\');6 2a=[];q(6 i=0,G=4.h.9;i<G;i++){q(6 j=0;j<G;j++){7(4.h[i].1t==7F[j]){2a.I(j);2z}}}m 2a}d 3G(4){6 4W=\'\';q(6 i=0,G=4.h.9;i<G;i++){4W+=4.h[i].1t+\',\'}7(4W.9==G){m""}m 4W.1w(0,-1)}d 3s(4){4.o.9=0;4.17.9=0;4.E.9=0;1B(4)}d 4y(4){7(!4.C.2P){m}6 13="{";13+=\'"37": \'+4.D+\',\';13+=\'"3F": \'+4.1H+\',\';13+=\'"31": \'+4.T+\',\';13+=\'"7G": "\'+4.1g.18.X(\'"\',\'\\\\"\')+\'",\';13+=\'"6q": \'+4.1g.R+\',\';13+=\'"M": [ \';q(6 i=0;i<4.M.9;i++){13+="["+4.M[i][0]+",\'"+4.M[i][1]+"\'],"}13=13.4X(0,13.9-1);13+="],";13+=\'"3U": [ \';q(6 i=0;i<4.1r.9;i++){13+="[\'"+4.1r[i].18.X("\'","\\\'")+"\',"+4.1r[i].R+"],"}13=13.4X(0,13.9-1);13+="]";13+="}";4Y("7H"+4.3o,13,4.70)}d 4Z(4){7(!4.C.2P){m}6 16=51("7H"+4.3o);7(16!==s&&16!==\'\'){9v{7(w 6r==\'4l\'&&w 6r.1P==\'d\'){1y=6r.1P(16.X(/\'/g,\'"\'))}u{1y=7u(\'(\'+16+\')\')}}9w(e){m}4.D=1y.37;4.2H=1y.37;4.1H=1y.3F;4.T=1y.31;4.1g.18=1y.7G;4.M=1y.M.1w();7(w 1y.6q!=\'H\'){4.1g.R=1y.6q}7(w 1y.3U!=\'H\'){q(6 i=0;i<1y.3U.9;i++){4.1r[i]={"18":1y.3U[i][0],"R":1y.3U[i][1]}}}}}d 4Y(1t,13,7I){6 29=3N 21();29.9x(29.9y()+(7I*9z));1t+=\'7J\'+68.7K.7L.X(/[\\/:]/g,"").20();J.7M=1t+"="+13+"; 9A="+29.9B()+"; 9C=/"}d 51(1t){6 6s=1t+\'7J\'+68.7K.7L.X(/[\\/:]/g,"").20()+"=";6 6t=J.7M.3J(\';\');q(6 i=0;i<6t.9;i++){6 c=6t[i];5Y(c.6T(0)==\' \'){c=c.4X(1,c.9)}7(c.5f(6s)===0){m c.4X(6s.9,c.9)}}m s}d 52(3V){6 1E=3V.1c(\'1o\');7(1E.9==1){m 1E[0].1c(\'1W\')}6 1J=[],6u=[];6 7N=2,7O=3;6 i,j,k,G,28,3a;6 6v=d(a,i,j){5Y(w a[i][j]!=\'H\'){j++}m j};6 6w=d(i){7(w 1J[i]==\'H\'){1J[i]=[]}};q(i=0,G=1E.9;i<G;i++){6w(i);6 U=0;6 3W=1E[i].1c(\'1W\');q(j=0,28=3W.9;j<28;j++){6 3b=3W[j].4T(\'9D\')*1;6 3X=3W[j].4T(\'9E\')*1;7(!3b||3b==0||3b==1){3a=6v(1J,i,U);1J[i][3a]=3W[j];7(3X||3X==0||3X==1){q(k=1;k<3X;k++){6w(i+k);1J[i+k][3a]=7N}}U++}u{3a=6v(1J,i,U);q(k=0;k<3b;k++){1J[i][3a+k]=7O}U+=3b}}}q(i=0,G=1J[0].9;i<G;i++){q(j=0,28=1J.9;j<28;j++){7(w 1J[j][i]==\'4l\'){6u.I(1J[j][i])}}}m 6u}d A(7P,6x,1t,53){7(w 53==\'H\'){53=1t}7(w 6x[1t]!=\'H\'){7P[53]=6x[1t]}}7(5H){8.B.2Z=2Z;8.B.3B=3B;8.B.4s=4s;8.B.2b=2b;8.B.4v=4v;8.B.4r=4r;8.B.1x=1x;8.B.4w=4w;8.B.4q=4q;8.B.4F=4F;8.B.4H=4H;8.B.4I=4I;8.B.4E=4E;8.B.4G=4G;8.B.2f=2f;8.B.2l=2l;8.B.4L=4L;8.B.4K=4K;8.B.30=30;8.B.69=69;8.B.2o=2o;8.B.3u=3u;8.B.2m=2m;8.B.3O=3O;8.B.1B=1B;8.B.4P=4P;8.B.4Q=4Q;8.B.6n=6n;8.B.2C=2C;8.B.2V=2V;8.B.3v=3v;8.B.3L=3L;8.B.4A=4A;8.B.3G=3G;8.B.3s=3s;8.B.4y=4y;8.B.4Z=4Z;8.B.4Y=4Y;8.B.51=51;8.B.52=52}m 8.5K(d(){6 4=3N 6W();3m.I(4);6 i=0,G;6 6y=K;6 6z=K;6 54=8.4T(\'1M\');7(54!==s){4.1f=54;4.3o=54}u{4.3o=$.14.1e.6U.6V++}4.L=8;7(w z!=\'H\'&&z!==s){A(4.C,z,"2O");A(4.C,z,"5i");A(4.C,z,"3p");A(4.C,z,"2x");A(4.C,z,"49");A(4.C,z,"3q");A(4.C,z,"4a");A(4.C,z,"5j");A(4.C,z,"22");A(4,z,"2y");A(4,z,"4e");A(4,z,"4f");A(4,z,"4g");A(4,z,"4h");A(4,z,"2R");A(4,z,"5x");A(4,z,"M");A(4,z,"1S");A(4,z,"4i");A(4,z,"2S");A(4,z,"2F","5w");A(4,z,"9F","1g");A(4,z,"9G","1r");A(4,z,"7h","T");7(w z.4z!=\'H\'&&w 4.2H==\'H\'){4.2H=z.4z;4.D=z.4z}7(w z.2q!=\'H\'){6z=F}7(w z.2P!=\'H\'){4.C.2P=z.2P;4Z(4)}7(w z!=\'H\'&&w z.o!=\'H\'){z.h=z.o}7(w z.v!=\'H\'){7(w z.v.3r!=\'H\'){4.v.3r=z.v.3r;$.5y(4.v.3r,s,d(1p){3B(4,1p,F)});6y=F}u{3B(4,z.v,K)}}}6 3V=8.1c(\'2e\');6 55=3V.9===0?s:52(3V[0]);6 6A=w z!=\'H\'&&w z.h!=\'H\';q(i=0,G=6A?z.h.9:55.9;i<G;i++){6 7Q=6A?z.h[i]:s;6 n=55?55[i]:s;4s(4,7Q,n)}7(8.1c(\'2e\').9===0){8.O(J.Q(\'2e\'))}7(8.1c(\'33\').9===0){8.O(J.Q(\'33\'))}7(6z){q(i=0;i<z.2q.9;i++){2b(4,z.2q[i])}}u{4v(4)}4.E=4.17.1w();7(4.C.4a){4Q(4)}4.5v=F;7(6y===K){2Z(4)}})}})(6O);', 62, 601, '||||oSettings||var|if|this|length||||function||||aoColumns|||||return||aoData||for||null||else|oLanguage|typeof|||oInit|_fnMap|oApi|oFeatures|_iDisplayStart|aiDisplay|true|iLen|undefined|push|document|false|nTable|aaSorting|_aData|appendChild|nTr|createElement|bEscapeRegex|sType|_iDisplayLength|iColumn|nTh|className|replace|aaSort|bVisible||nTd|sWidth|sValue|fn|innerHTML|sData|aiDisplayMaster|sSearch|iCol|value|td|getElementsByTagName|aData|dataTableExt|sTableId|oPreviousSearch|nPaginate|iRow|iDataSort|oPaginate|_fnSettingsFromNode|iTest|sClass|tr|json|name|aoPreSearchCols|oOptions|sName|sInput|bRedraw|slice|_fnDraw|oData|nPrevious|nNext|_fnCalculateEnd|iCorrector|oCol|nTrs|fnRecordsDisplay|00|_iDisplayEnd|parentNode|aLayout|fnCallbackDraw|div|id|span|asc|parse|nInfo|nProcessing|aaSortingFixed|mData|splice|sDisplay|th|nInsertNode|nCalcTmp|setAttribute|toLowerCase|Date|bServerSide|aoOpenRows|fnRender||iThisIndex|style|jLen|date|aiReturn|_fnAddData|removeChild|iColumns|thead|_fnProcessingDisplay|cOption|option|iPages|01|asDataSearch|_fnFilterComplete|_fnBuildSearchArray|nParent|_fnColumnIndexToVisible|nTrFoot|aaData|sTitle|anRows|iForce|click|iPageCount|iStartButton|bSort|asStripClasses|break|iVisibleColumn|_anHidden|_fnDetectType|iDataIndex|nRow|sDom|nLength|iInitDisplayStart|iVis|cPage|cpage|iEndButton|oSort|desc|bPaginate|bStateSave|sInfoPostFix|fnInitComplete|sAjaxSource|nNode|_fnSort|_fnGetDataMaster|bUseRendered|iBefore|sFunc|_fnInitalise|_fnSortingClasses|iLength|sRendered|tbody|eq|nCellNode|width|iStart|sColumns|nFilter|iColumnShifted|iColspan|fnUpdate|nFirst|nLast|paginate_button|bind|iCurrentPage|string|isNaN|numeric|aTypes|_aoSettings|fnRecordsTotal|sInstance|bFilter|bProcessing|sUrl|_fnClearTable|nNewCell|_fnVisbleColumns|_fnGetTrNodes|bShow|nTrHead|anTheadTh|anTfootTh|insertBefore|_fnLanguageProcess|offsetWidth|px|iRowCount|iEnd|_fnColumnOrdering|iFixed|nWrapper|split|iIndexCorrector|_fnEscapeRegex|rpSearch|new|_fnDataToSearch|iDataType|aAirSort|nTmp|iColums|sCalcHtml|aaSearchCols|nThead|nTds|iRowspan|afnFiltering|oPagination|sPrevious|sNext|fnDisplayEnd|parseInt|nPaginateList|sList|html|1970|Char|bInfo|bAutoWidth|sInfo|sInfoEmpty|sInfoFiltered|fnRowCallback|fnHeaderCallback|fnFooterCallback|fnDrawCallback|sPaginationType|bAjaxDataGet|_fnReDraw|object|iAODataIndex|nNewRow|iDataRow|iDataColumn|_fnAddOptionsHtml|_fnDrawHead|_fnAddColumn|_bAutoType|bSortable|_fnGatherData|_fnAjaxUpdate|addClass|_fnSaveState|iDisplayStart|_fnReOrderIndex|nHolding|nNewNode|cNext|_fnFeatureHtmlLength|_fnFeatureHtmlFilter|_fnFeatureHtmlProcessing|_fnFeatureHtmlInfo|_fnFeatureHtmlPaginate|select|_fnFilter|_fnFilterColumn|sDynamicSort|iClass|iMatch|_fnConvertToWidth|_fnCalculateColumnWidths|iTmpWidth|sCalcHead|getAttribute|iMaxIndex|aArray1|sNames|substring|_fnCreateCookie|_fnLoadState||_fnReadCookie|_fnGetUniqueThs|sMappedName|sId|nThs|fnInit|input|sFirst|sLast|mousedown|iPageCountHalf|class|val|bDecimal|indexOf|_iRecordsTotal|_iRecordsDisplay|bLengthChange|bSortClasses|sProcessing|sLengthMenu|sZeroRecords|records|_START_|to|_END_|of|_TOTAL_|_MAX_|iNextId|bInitialised|sDomPositioning|fnServerData|getJSON|fnCallBack|nTrParent|table|in|tfoot|nTf|iInsert|concat|bApi|bSearchable|iLoop|each|bAutoType|bRender|bClass|fnInnerSorting|bFound|nTfoot|nTfs|iOpenRows|removeClass|join|iUser|sOrdering|bReOrder|while|visibility||hidden|oInput|afnFilters|iDisIndex|RegExp|test|bApplyClasses|window|_fnVisibleToColumnIndex|iVisible|oS|iMaster|aArray|iWidth|iTotalUserIpSize|iVisibleColumns|oHeaders|tag_index|fnGetMaxLenString|oNodes|iIndex|iMax|_fnArrayCmp|aArray2|aNodes|sFilterEsc|JSON|sNameEQ|sCookieContents|aReturn|fnShiftCol|fnAddRow|oSrc|bInitHandedOff|bUsePassedData|bUseCols|dataTableSettings|two_button|paginate_disabled_previous|paginate_disabled_next|title|insertAfter|iFullNumbersShowPages|img|nList|type|text|src|selectstart|jQuery|Math|keyup|iTarget|sValidChars|charAt|_oExternConfig|iNextUnique|classSettings|_MENU_|Showing|nbsp|iCookieDuration|call|sHtml|fnClose|colSpan|nodeName|the|iVisCol|_fnExternApiFunc|aArgs|setTimeout|bInit|_iId|aLocalData|iThs|iStrips|nBody|iDisplayLength|iSortCol_|iSortDir_|_fnAjaxUpdateDraw|aiIndex|_length|sStdMenu|100|_fnFilterCustom|sRegexMatch|asSearch|sRegExpString|fnLocalSorting|eval|sort|sorting_asc|sorting_desc|sorting|sorting_3|iTableWidth|sTableTmp|sVal|acEscape|reReplace|aColumns|sFilter|SpryMedia_DataTables_|iSecs|_|location|pathname|cookie|ROWSPAN|COLSPAN|oRet|col|_paginate|_previous|_next|paginate_enabled_previous|paginate_enabled_next|full_numbers|first|previous|next|last|floor|ceil|paginate_active|0123456789|dataTable|Processing|Show|per|page|No|matching|found|entries|filtered|from|total|Search|odd|even|iDefaultSortIndex|lfrtip|fnDraw|fnFilter|fnSettings|fnSort|fnAddData|fnDeleteRow|fnClearTable|fnOpen|fnGetData|fnGetNodes|fnGetPosition|TR|TD|alert|Warning|An|array|passed|must|have|same|number|columns|as|question|case|fnSetColumnVis|Array|prototype|arguments|apply|200|nDataNodes|iDataLength|shiftKey|onselectstart|valign|top|dataTables_empty|sSearch_|bEscapeRegex_|iSortingCols|iTotalRecords|iTotalDisplayRecords|dataTables_wrapper|_wrapper|replaceChild|_filter|dataTables_filter|quot|_info|dataTables_info|dataTables_paginate|size|dataTables_length|attr|selected|change|_processing|createTextNode|dataTables_processing|visible|flag|runtime|sorting_1|sorting_2|sorting_|body|cloneNode|position|absolute|try|catch|setTime|getTime|1000|expires|toGMTString|path|colspan|rowspan|oSearch|aoSearchCols'.split('|'), 0, {}))
/*
 * File:        jquery.dataTables.js
 * Version:     1.5.0 beta 7
 * CVS:         $Id$
 * Description: Paginate, search and sort HTML tables
 * Author:      Allan Jardine (www.sprymedia.co.uk)
 * Created:     28/3/2008
 * Modified:    $Date$ by $Author$
 * Language:    Javascript
 * License:     GPL v2 or BSD 3 point style
 * Project:     Mtaala
 * Contact:     allan.jardine@sprymedia.co.uk
 * 
 * Copyright 2008-2009 Allan Jardine, all rights reserved.
 *
 * This source file is free software, under either the GPL v2 license or a
 * BSD style license, as supplied with this software.
 * 
 * This source file is distributed in the hope that it will be useful, but 
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
 * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
 * 
 * For details pleease refer to: http://sprymedia.co.uk/article/DataTables
 */

/*
 * When considering jsLint, we need to allow eval() as it it is used for reading cookies and 
 * building the dynamic multi-column sort functions.
 */
/*jslint evil: true */

(function($) {
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * DataTables variables
	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
	
	/*
	 * Variable: dataTableSettings
	 * Purpose:  Store the settings for each dataTables instance
	 * Scope:    jQuery.fn
	 */
	$.fn.dataTableSettings = [];
	
	/*
	 * Variable: dataTableExt
	 * Purpose:  Container for customisable parts of DataTables
	 * Scope:    jQuery.fn
	 */
	$.fn.dataTableExt = {};
	
	
	
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * DataTables extensible objects
	 * 
	 * The $.fn.dataTableExt object is used to provide an area where user dfined plugins can be 
	 * added to DataTables. The following properties of the object are used:
	 *   oApi - Plug-in API functions
	 *   aTypes - Auto-detection of types
	 *   oSort - Sorting functions used by DataTables (based on the type)
	 *   oPagination - Pagination functions for different input styles
	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
	
	/*
	 * Variable: oApi
	 * Purpose:  Container for plugin API functions
	 * Scope:    jQuery.fn.dataTableExt
	 */
	$.fn.dataTableExt.oApi = { };
	
	/*
	 * Variable: aFiltering
	 * Purpose:  Container for plugin filtering functions
	 * Scope:    jQuery.fn.dataTableExt
	 */
	$.fn.dataTableExt.afnFiltering = [ ];
	
	/*
	 * Variable: oPagination
	 * Purpose:  Container for the various type of pagination that dataTables supports
	 * Scope:    jQuery.fn.dataTableExt
	 */
	$.fn.dataTableExt.oPagination = {
		/*
		 * Variable: two_button
		 * Purpose:  Standard two button (forward/back) pagination
	 	 * Scope:    jQuery.fn.dataTableExt.oPagination
		 */
		"two_button": {
			/*
			 * Function: oPagination.two_button.fnInit
			 * Purpose:  Initalise dom elements required for pagination with forward/back buttons only
			 * Returns:  -
	 		 * Inputs:   object:oSettings - dataTables settings object
			 *           function:fnCallbackDraw - draw function which must be called on update
			 */
			"fnInit": function ( oSettings, fnCallbackDraw )
			{
				oSettings.nPrevious = document.createElement( 'div' );
				oSettings.nNext = document.createElement( 'div' );
				
				if ( oSettings.sTableId !== '' )
				{
					oSettings.nPaginate.setAttribute( 'id', oSettings.sTableId+'_paginate' );
					oSettings.nPrevious.setAttribute( 'id', oSettings.sTableId+'_previous' );
					oSettings.nNext.setAttribute( 'id', oSettings.sTableId+'_next' );
				}
				
				oSettings.nPrevious.className = "paginate_disabled_previous";
				oSettings.nNext.className = "paginate_disabled_next";
				
				oSettings.nPrevious.title = oSettings.oLanguage.oPaginate.sPrevious;
				oSettings.nNext.title = oSettings.oLanguage.oPaginate.sNext;
				
				oSettings.nPaginate.appendChild( oSettings.nPrevious );
				oSettings.nPaginate.appendChild( oSettings.nNext );
				$(oSettings.nPaginate).insertAfter( oSettings.nTable );
				
				$(oSettings.nPrevious).click( function() {
					oSettings._iDisplayStart -= oSettings._iDisplayLength;
					
					/* Correct for underrun */
					if ( oSettings._iDisplayStart < 0 )
					{
					  oSettings._iDisplayStart = 0;
					}
					
					fnCallbackDraw( oSettings );
				} );
				
				$(oSettings.nNext).click( function() {
					/* Make sure we are not over running the display array */
					if ( oSettings._iDisplayStart + oSettings._iDisplayLength < oSettings.fnRecordsDisplay() )
					{
						oSettings._iDisplayStart += oSettings._iDisplayLength;
					}
					
					fnCallbackDraw( oSettings );
				} );
			},
			
			/*
			 * Function: oPagination.two_button.fnUpdate
			 * Purpose:  Update the two button pagination at the end of the draw
			 * Returns:  -
	 		 * Inputs:   object:oSettings - dataTables settings object
			 *           function:fnCallbackDraw - draw function which must be called on update
			 */
			"fnUpdate": function ( oSettings, fnCallbackDraw )
			{
				oSettings.nPrevious.className = 
					( oSettings._iDisplayStart === 0 ) ? 
					"paginate_disabled_previous" : "paginate_enabled_previous";
				
				oSettings.nNext.className = 
					( oSettings.fnDisplayEnd() == oSettings.fnRecordsDisplay() ) ? 
					"paginate_disabled_next" : "paginate_enabled_next";
			}
		},
		
		
		/*
		 * Variable: iFullNumbersShowPages
		 * Purpose:  Change the number of pages which can be seen
	 	 * Scope:    jQuery.fn.dataTableExt.oPagination
		 */
		"iFullNumbersShowPages": 5,
		
		/*
		 * Variable: full_numbers
		 * Purpose:  Full numbers pagination
	 	 * Scope:    jQuery.fn.dataTableExt.oPagination
		 */
		"full_numbers": {
			/*
			 * Function: oPagination.full_numbers.fnInit
			 * Purpose:  Initalise dom elements required for pagination with a list of the pages
			 * Returns:  -
	 		 * Inputs:   object:oSettings - dataTables settings object
			 *           function:fnCallbackDraw - draw function which must be called on update
			 */
			"fnInit": function ( oSettings, fnCallbackDraw )
			{
				var nFirst = document.createElement( 'img' );
				var nPrevious = document.createElement( 'span' );
				var nList = document.createElement( 'span' );
				var nNext = document.createElement( 'span' );
				var nLast = document.createElement( 'img' );
				var cPage = document.createElement( 'input' );
				
				
				cPage.type="text";
				cPage.id="cpage";
				cPage.name="cpage";
				cPage.className="cpage";
				nFirst.src=oSettings.oLanguage.oPaginate.sFirst;
				nLast.src=oSettings.oLanguage.oPaginate.sLast;
				
				//nFirst.innerHTML = oSettings.oLanguage.oPaginate.sFirst;
				nPrevious.innerHTML = oSettings.oLanguage.oPaginate.sPrevious;
				nNext.innerHTML = oSettings.oLanguage.oPaginate.sNext;
				//nLast.innerHTML = oSettings.oLanguage.oPaginate.sLast;
				
				nFirst.className = "paginate_button first";
				nPrevious.className = "paginate_button previous";
				nNext.className="paginate_button next";
				nLast.className = "paginate_button last";
				
				oSettings.nPaginate.appendChild( nFirst );
				oSettings.nPaginate.appendChild( nPrevious );
			//	oSettings.nPaginate.appendChild( nList );
				oSettings.nPaginate.appendChild( cPage );//nList
				oSettings.nPaginate.appendChild( nNext );
				oSettings.nPaginate.appendChild( nLast );
				
				$(nFirst).click( function () {
					oSettings._iDisplayStart = 0;
					fnCallbackDraw( oSettings );
				} );
				
				$(nPrevious).click( function() {
					oSettings._iDisplayStart -= oSettings._iDisplayLength;
					
					/* Correct for underrun */
					if ( oSettings._iDisplayStart < 0 )
					{
					  oSettings._iDisplayStart = 0;
					}
					
					fnCallbackDraw( oSettings );
				} );
				
				$(nNext).click( function() {
					/* Make sure we are not over running the display array */
					if ( oSettings._iDisplayStart + oSettings._iDisplayLength < oSettings.fnRecordsDisplay() )
					{
						oSettings._iDisplayStart += oSettings._iDisplayLength;
					}
					
					fnCallbackDraw( oSettings );
				} );
				
				$(nLast).click( function() {
					var iPages = parseInt( (oSettings.fnRecordsDisplay()-1) / oSettings._iDisplayLength, 10 ) + 1;
					oSettings._iDisplayStart = (iPages-1) * oSettings._iDisplayLength;
					
					fnCallbackDraw( oSettings );
				} );
				
				/* Take the brutal approach to cancelling text selection */
				$('span', oSettings.nPaginate).bind( 'mousedown', function () { return false; } );
				$('span', oSettings.nPaginate).bind( 'selectstart', function () { return false; } );
				
				oSettings.nPaginateList = nList;
			},
			
			/*
			 * Function: oPagination.full_numbers.fnUpdate
			 * Purpose:  Update the list of page buttons shows
			 * Returns:  -
	 		 * Inputs:   object:oSettings - dataTables settings object
			 *           function:fnCallbackDraw - draw function which must be called on update
			 */
			"fnUpdate": function ( oSettings, fnCallbackDraw )
			{
				var iPageCount = jQuery.fn.dataTableExt.oPagination.iFullNumbersShowPages;
				var iPageCountHalf = Math.floor(iPageCount / 2);
				var iPages = parseInt( (oSettings.fnRecordsDisplay()-1) / oSettings._iDisplayLength, 10 ) + 1;
				var iCurrentPage = parseInt( oSettings._iDisplayStart / oSettings._iDisplayLength, 10 ) + 1;
				var sList = "";
				var iStartButton;
				var iEndButton;
				
				if (iPages < iPageCount)
				{
					iStartButton = 1;
					iEndButton = iPages;
				}
				else
				{
					if (iCurrentPage <= iPageCountHalf)
					{
						iStartButton = 1;
						iEndButton = iPageCount;
					}
					else
					{
						if (iCurrentPage >= (iPages - iPageCountHalf))
						{
							iStartButton = iPages - iPageCount + 1;
							iEndButton = iPages;
						}
						else
						{
							iStartButton = iCurrentPage - Math.ceil(iPageCount / 2) + 1;
							iEndButton = iStartButton + iPageCount - 1;
						}
					}
				}
				
				for ( var i=iStartButton ; i<=iEndButton ; i++ )
				{
				    //$('#cpage').val(i);
					if ( iCurrentPage != i )
					{
						sList += '<span class="paginate_button">'+i+'</span>';
					}
					else
					{
						sList += '<span class="paginate_active">'+i+'</span>';
						$('#cpage').val(i);
					}
				}
				
				oSettings.nPaginateList.innerHTML = sList;
				
				/* Take the brutal approach to cancelling text selection */
				$('span', oSettings.nPaginateList).bind( 'mousedown', function () { return false; } );
				$('span', oSettings.nPaginateList).bind( 'selectstart', function () { return false; } );
				
				$('#cpage').bind('keyup',function()
				{
				   
				    var iTarget = ($('#cpage').val()*1)-1;//(this.innerHTML * 1) - 1;
					//alert(iTarget);
					oSettings._iDisplayStart = iTarget * oSettings._iDisplayLength;
					
					fnCallbackDraw( oSettings );
					return false;
				});
				
//				$('span', oSettings.nPaginateList).click( function() {
//				    alert('ok');
//					var iTarget = (this.innerHTML * 1) - 1;
//					oSettings._iDisplayStart = iTarget * oSettings._iDisplayLength;
//					
//					fnCallbackDraw( oSettings );
//					return false;
//				} );
			}
		}
	};
	
	/*
	 * Variable: oSort
	 * Purpose:  Wrapper for the sorting functions that can be used in DataTables
	 * Scope:    jQuery.fn.dataTableExt
	 * Notes:    The functions provided in this object are basically standard javascript sort
	 *   functions - they expect two inputs which they then compare and then return a priority
	 *   result. For each sort method added, two functions need to be defined, an ascending sort and
	 *   a descending sort.
	 */
	$.fn.dataTableExt.oSort = {
		/*
		 * text sorting
		 */
		"string-asc": function ( a, b )
		{
			var x = a.toLowerCase();
			var y = b.toLowerCase();
			return ((x < y) ? -1 : ((x > y) ? 1 : 0));
		},
		
		"string-desc": function ( a, b )
		{
			var x = a.toLowerCase();
			var y = b.toLowerCase();
			return ((x < y) ? 1 : ((x > y) ? -1 : 0));
		},
		
		
		/*
		 * html sorting (ignore html tags)
		 */
		"html-asc": function ( a, b )
		{
			var x = a.replace( /<.*?>/g, "" ).toLowerCase();
			var y = b.replace( /<.*?>/g, "" ).toLowerCase();
			return ((x < y) ? -1 : ((x > y) ? 1 : 0));
		},
		
		"html-desc": function ( a, b )
		{
			var x = a.replace( /<.*?>/g, "" ).toLowerCase();
			var y = b.replace( /<.*?>/g, "" ).toLowerCase();
			return ((x < y) ? 1 : ((x > y) ? -1 : 0));
		},
		
		
		/*
		 * date sorting
		 */
		"date-asc": function ( a, b )
		{
			var x = Date.parse( a );
			var y = Date.parse( b );
			
			if ( isNaN( x ) )
			{
    		x = Date.parse( "01/01/1970 00:00:00" );
			}
			if ( isNaN( y ) )
			{
				y =	Date.parse( "01/01/1970 00:00:00" );
			}
			
			return x - y;
		},
		
		"date-desc": function ( a, b )
		{
			var x = Date.parse( a );
			var y = Date.parse( b );
			
			if ( isNaN( x ) )
			{
    		x = Date.parse( "01/01/1970 00:00:00" );
			}
			if ( isNaN( y ) )
			{
				y =	Date.parse( "01/01/1970 00:00:00" );
			}
			
			return y - x;
		},
		
		
		/*
		 * numerical sorting
		 */
		"numeric-asc": function ( a, b )
		{
			var x = a == "-" ? 0 : a;
			var y = b == "-" ? 0 : b;
			return x - y;
		},
		
		"numeric-desc": function ( a, b )
		{
			var x = a == "-" ? 0 : a;
			var y = b == "-" ? 0 : b;
			return y - x;
		}
	};
	
	
	/*
	 * Variable: aTypes
	 * Purpose:  Container for the various type of type detection that dataTables supports
	 * Scope:    jQuery.fn.dataTableExt
	 * Notes:    The functions in this array are expected to parse a string to see if it is a data
	 *   type that it recognises. If so then the function should return the name of the type (a
	 *   corresponding sort function should be defined!), if the type is not recognised then the
	 *   function should return null such that the parser and move on to check the next type.
	 *   Note that ordering is important in this array - the functions are processed linearly,
	 *   starting at index 0.
	 */
	$.fn.dataTableExt.aTypes = [
		/*
		 * Function: -
		 * Purpose:  Check to see if a string is numeric
		 * Returns:  string:'numeric' or null
		 * Inputs:   string:sText - string to check
		 */
		function ( sData )
		{
			var sValidChars = "0123456789.-";
			var Char;
			var bDecimal = false;
			
			for ( i=0 ; i<sData.length ; i++ ) 
			{ 
				Char = sData.charAt(i); 
				if (sValidChars.indexOf(Char) == -1) 
				{
					return null;
				}
				
				/* Only allowed one decimal place... */
				if ( Char == "." )
				{
					if ( bDecimal )
					{
						return null;
					}
					bDecimal = true;
				}
			}
			
			return 'numeric';
		},
		
		/*
		 * Function: -
		 * Purpose:  Check to see if a string is actually a formatted date
		 * Returns:  string:'date' or null
		 * Inputs:   string:sText - string to check
		 */
		function ( sData )
		{
			if ( ! isNaN(Date.parse(sData) ) )
			{
				return 'date';
			}
			return null;
		}
	];
	
	
	/*
	 * Variable: _oExternConfig
	 * Purpose:  Store information for DataTables to access globally about other instances
	 * Scope:    jQuery.fn.dataTableExt
	 */
	$.fn.dataTableExt._oExternConfig = {
		/* int:iNextUnique - next unique number for an instance */
		"iNextUnique": 0
	};
	
	
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * DataTables prototype
	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
	
	/*
	 * Function: dataTable
	 * Purpose:  DataTables information
	 * Returns:  -
	 * Inputs:   object:oInit - initalisation options for the table
	 */
	$.fn.dataTable = function( oInit )
	{
		/*
		 * Variable: _aoSettings
		 * Purpose:  Easy reference to data table settings
		 * Scope:    jQuery.dataTable
		 */
		var _aoSettings = $.fn.dataTableSettings;
		
		/*
		 * Function: classSettings
		 * Purpose:  Settings container function for all 'class' properties which are required
		 *   by dataTables
		 * Returns:  -
		 * Inputs:   -
		 */
		function classSettings ()
		{
			this.fnRecordsTotal = function ()
			{
				if ( this.oFeatures.bServerSide ) {
					return this._iRecordsTotal;
				} else {
					return this.aiDisplayMaster.length;
				}
			};
			
			this.fnRecordsDisplay = function ()
			{
				if ( this.oFeatures.bServerSide ) {
					return this._iRecordsDisplay;
				} else {
					return this.aiDisplay.length;
				}
			};
			
			this.fnDisplayEnd = function ()
			{
				if ( this.oFeatures.bServerSide ) {
					return this._iDisplayStart + this.aiDisplay.length;
				} else {
					return this._iDisplayEnd;
				}
			};
			
			/*
			 * Variable: sInstance
			 * Purpose:  Unique idendifier for each instance of the DataTables object
			 * Scope:    jQuery.dataTable.classSettings 
			 */
			this.sInstance = null;
			
			/*
			 * Variable: oFeatures
			 * Purpose:  Indicate the enablement of key dataTable features
			 * Scope:    jQuery.dataTable.classSettings 
			 */
			this.oFeatures = {
				"bPaginate": true,
				"bLengthChange": true,
				"bFilter": true,
				"bSort": true,
				"bInfo": true,
				"bAutoWidth": true,
				"bProcessing": false,
				"bSortClasses": true,
				"bStateSave": false,
				"bServerSide": false
			};
			
			/*
			 * Variable: oLanguage
			 * Purpose:  Store the language strings used by dataTables
			 * Scope:    jQuery.dataTable.classSettings
			 * Notes:    The words in the format _VAR_ are variables which are dynamically replaced
			 *   by javascript
			 */
			this.oLanguage = {
				"sProcessing": "Processing...",
				"sLengthMenu": "Show _MENU_ per page",
				"sZeroRecords": "No matching records found",
				"sInfo": "Showing _START_ to _END_ of _TOTAL_ records",
				"sInfoEmpty": "Showing 0 to 0 of 0 entries",
				"sInfoFiltered": "(filtered from _MAX_ total records)",
				"sInfoPostFix": "",
				"sSearch": "Search:",
				"sUrl": "",
//				"oPaginate": {
//					"sFirst":    "First",
//					"sPrevious": "Previous",
//					"sNext":     "Next",
//					"sLast":     "Last"
//				}
                "oPaginate": {
					"sFirst":    "&nbsp;&nbsp;",
					"sPrevious": "",
					"sNext":     "",
					"sLast":     ""
				}
			};
			
			/*
			 * Variable: aoData
			 * Purpose:  Store data information
			 * Scope:    jQuery.dataTable.classSettings 
			 * Notes:    This is an array of objects with the following parameters:
			 *   int: _iId - internal id for tracking
			 *   array: _aData - internal data - used for sorting / filtering etc
			 *   node: nTr - display node
			 *   array node: _anHidden - hidden TD nodes
			 */
			this.aoData = [];
			
			/*
			 * Variable: aiDisplay
			 * Purpose:  Array of indexes which are in the current display (after filtering etc)
			 * Scope:    jQuery.dataTable.classSettings
			 */
			this.aiDisplay = [];
			
			/*
			 * Variable: aiDisplayMaster
			 * Purpose:  Array of indexes for display - no filtering
			 * Scope:    jQuery.dataTable.classSettings
			 */
			this.aiDisplayMaster = [];
							
			/*
			 * Variable: aoColumns
			 * Purpose:  Store information about each column that is in use
			 * Scope:    jQuery.dataTable.classSettings 
			 */
			this.aoColumns = [];
			
			/*
			 * Variable: iNextId
			 * Purpose:  Store the next unique id to be used for a new row
			 * Scope:    jQuery.dataTable.classSettings 
			 */
			this.iNextId = 0;
			
			/*
			 * Variable: asDataSearch
			 * Purpose:  Search data array for regular expression searching
			 * Scope:    jQuery.dataTable.classSettings
			 */
			this.asDataSearch = [];
			
			/*
			 * Variable: oPreviousSearch
			 * Purpose:  Store the previous search incase we want to force a re-search
			 *   or compare the old search to a new one
			 * Scope:    jQuery.dataTable.classSettings
			 */
			this.oPreviousSearch = {
				"sSearch": "",
				"bEscapeRegex": true
			};
			
			/*
			 * Variable: aoPreSearchCols
			 * Purpose:  Store the previous search for each column
			 * Scope:    jQuery.dataTable.classSettings
			 */
			this.aoPreSearchCols = [];
			
			/*
			 * Variable: nInfo
			 * Purpose:  Info display for user to see what records are displayed
			 * Scope:    jQuery.dataTable.classSettings
			 */
			this.nInfo = null;
			
			/*
			 * Variable: nProcessing
			 * Purpose:  Processing indicator div
			 * Scope:    jQuery.dataTable.classSettings
			 */
			this.nProcessing = null;
			
			/*
			 * Variable: aaSorting
			 * Purpose:  Sorting information
			 * Scope:    jQuery.dataTable.classSettings
			 */
			this.aaSorting = [ [0, 'asc'] ];
			
			/*
			 * Variable: aaSortingFixed
			 * Purpose:  Sorting information that is always applied
			 * Scope:    jQuery.dataTable.classSettings
			 */
			this.aaSortingFixed = null;
			
			/*
			 * Variable: asStripClasses
			 * Purpose:  Classes to use for the striping of a table
			 * Scope:    jQuery.dataTable.classSettings
			 */
			this.asStripClasses = [ 'odd', 'even' ];
			
			/*
			 * Variable: fnRowCallback
			 * Purpose:  Call this function every time a row is inserted (draw)
			 * Scope:    jQuery.dataTable.classSettings
			 */
			this.fnRowCallback = null;
			
			/*
			 * Variable: fnHeaderCallback
			 * Purpose:  Callback function for the header on each draw
			 * Scope:    jQuery.dataTable.classSettings
			 */
			this.fnHeaderCallback = null;
			
			/*
			 * Variable: fnFooterCallback
			 * Purpose:  Callback function for the footer on each draw
			 * Scope:    jQuery.dataTable.classSettings
			 */
			this.fnFooterCallback = null;
			
			/*
			 * Variable: fnDrawCallback
			 * Purpose:  Callback function for the whole table on each draw
			 * Scope:    jQuery.dataTable.classSettings
			 */
			this.fnDrawCallback = null;
			
			/*
			 * Variable: fnInitComplete
			 * Purpose:  Callback function for when the table has been initalised
			 * Scope:    jQuery.dataTable.classSettings
			 */
			this.fnInitComplete = null;
			
			/*
			 * Variable: sTableId
			 * Purpose:  Cache the table ID for quick access
			 * Scope:    jQuery.dataTable.classSettings
			 */
			this.sTableId = "";
			
			/*
			 * Variable: nTable
			 * Purpose:  Cache the table node for quick access
			 * Scope:    jQuery.dataTable.classSettings
			 */
			this.nTable = null;
			
			/*
			 * Variable: iDefaultSortIndex
			 * Purpose:  Sorting index which will be used by default
			 * Scope:    jQuery.dataTable.classSettings
			 */
			this.iDefaultSortIndex = 0;
			
			/*
			 * Variable: bInitialised
			 * Purpose:  Indicate if all required information has been read in
			 * Scope:    jQuery.dataTable.classSettings
			 */
			this.bInitialised = false;
			
			/*
			 * Variable: aoOpenRows
			 * Purpose:  Information about open rows
			 * Scope:    jQuery.dataTable.classSettings
			 * Notes:    Has the parameters 'nTr' and 'nParent'
			 */
			this.aoOpenRows = [];
			
			/*
			 * Variable: nPaginate, nPrevious, nNext
			 * Purpose:  Cache nodes for pagination
			 * Scope:    jQuery.dataTable.classSettings
			 */
			this.nPaginate = null;
			this.nPrevious = null;
			this.nNext = null;
			
			/*
			 * Variable: sDomPositioning
			 * Purpose:  Dictate the positioning that the created elements will take
			 * Scope:    jQuery.dataTable.classSettings
			 * Notes:    The following syntax is expected:
			 *   'l' - Length changing
			 *   'f' - Filtering input
			 *   't' - The table!
			 *   'i' - Information
			 *   'p' - Pagination
			 *   'r' - pRocessing
			 *   '<' and '>' - div elements
			 *   '<"class" and '>' - div with a class
			 *    Examples: '<"wrapper"flipt>', '<lf<t>ip>'
			 */
			this.sDomPositioning = 'lfrtip';
			
			/*
			 * Variable: sPaginationType
			 * Purpose:  Note which type of sorting should be used
			 * Scope:    jQuery.dataTable.classSettings
			 */
			this.sPaginationType = "two_button";
			
			/*
			 * Variable: iCookieDuration
			 * Purpose:  The cookie duration (for bStateSave) in seconds - default 2 hours
			 * Scope:    jQuery.dataTable.classSettings
			 */
			this.iCookieDuration = 60 * 60 * 2;
			
			/*
			 * Variable: sAjaxSource
			 * Purpose:  Source url for AJAX data for the table
			 * Scope:    jQuery.dataTable.classSettings
			 */
			this.sAjaxSource = null;
			
			/*
			 * Variable: bAjaxDataGet
			 * Purpose:  Note if draw should be blocked while getting data
			 * Scope:    jQuery.dataTable.classSettings
			 */
			this.bAjaxDataGet = true;
			
			/*
			 * Variable: fnServerData
			 * Purpose:  Function to get the server-side data - can be overruled by the developer
			 * Scope:    jQuery.dataTable.classSettings
			 */
			this.fnServerData = $.getJSON;
			
			/*
			 * Variable: _iDisplayLength, _iDisplayStart, _iDisplayEnd
			 * Purpose:  Display length variables
			 * Scope:    jQuery.dataTable.classSettings
			 * Notes:    These variable must NOT be used externally to get the data length. Rather, use
			 *   the fnRecordsTotal() (etc) functions.
			 */
			this._iDisplayLength = 10;
			this._iDisplayStart = 0;
			this._iDisplayEnd = 10;
			
			/*
			 * Variable: _iRecordsTotal, _iRecordsDisplay
			 * Purpose:  Display length variables used for server side processing
			 * Scope:    jQuery.dataTable.classSettings
			 * Notes:    These variable must NOT be used externally to get the data length. Rather, use
			 *   the fnRecordsTotal() (etc) functions.
			 */
			this._iRecordsTotal = 0;
			this._iRecordsDisplay = 0;
		}
		
		/*
		 * Variable: oApi
		 * Purpose:  Container for publicly exposed 'private' functions
		 * Scope:    jQuery.dataTable
		 */
		this.oApi = {};
		
		
		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
		 * API functions
		 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
		
		/*
		 * Function: fnDraw
		 * Purpose:  Redraw the table
		 * Returns:  -
		 * Inputs:   -
		 */
		this.fnDraw = function()
		{
			_fnReDraw( _fnSettingsFromNode( this[0] ) );
		};
		
		/*
		 * Function: fnFilter
		 * Purpose:  Filter the input based on data
		 * Returns:  -
		 * Inputs:   string:sInput - string to filter the table on
		 *           int:iColumn - optional - column to limit filtering to
		 *           bool:bEscapeRegex - optional - escape regex characters or not - default true
		 */
		this.fnFilter = function( sInput, iColumn, bEscapeRegex )
		{
			var oSettings = _fnSettingsFromNode( this[0] );
			
			if ( typeof bEscapeRegex == 'undefined' )
			{
				bEscapeRegex = true;
			}
			
			if ( typeof iColumn == "undefined" || iColumn === null )
			{
				/* Global filter */
				_fnFilterComplete( oSettings, {"sSearch":sInput, "bEscapeRegex": bEscapeRegex}, 1 );
			}
			else
			{
				/* Single column filter */
				oSettings.aoPreSearchCols[ iColumn ].sSearch = sInput;
				oSettings.aoPreSearchCols[ iColumn ].bEscapeRegex = bEscapeRegex;
				_fnFilterComplete( oSettings, oSettings.oPreviousSearch, 1 );
			}
		};
		
		/*
		 * Function: fnSettings
		 * Purpose:  Get the settings for a particular table for extern. manipulation
		 * Returns:  -
		 * Inputs:   -
		 */
		this.fnSettings = function( nNode  )
		{
			return _fnSettingsFromNode( this[0] );
		};
		
		/*
		 * Function: fnSort
		 * Purpose:  Sort the table by a particular row
		 * Returns:  -
		 * Inputs:   int:iCol - the data index to sort on. Note that this will
		 *   not match the 'display index' if you have hidden data entries
		 */
		this.fnSort = function( aaSort )
		{
			var oSettings = _fnSettingsFromNode( this[0] );
			oSettings.aaSorting = aaSort;
			_fnSort( oSettings );
		};
		
		/*
		 * Function: fnAddData
		 * Purpose:  Add new row(s) into the table
		 * Returns:  array int: array of indexes (aoData) which have been added (zero length on error)
		 * Inputs:   array:mData - the data to be added. The length must match
		 *               the original data from the DOM
		 *             or
		 *             array array:mData - 2D array of data to be added
		 *           bool:bRedraw - redraw the table or not - default true
		 * Notes:    Warning - the refilter here will cause the table to redraw
		 *             starting at zero
		 * Notes:    Thanks to Yekimov Denis for contributing the basis for this function!
		 */
		this.fnAddData = function( mData, bRedraw )
		{
			var aiReturn = [];
			var iTest;
			if ( typeof bRedraw == 'undefined' )
			{
				bRedraw = true;
			}
			
			/* Find settings from table node */
			var oSettings = _fnSettingsFromNode( this[0] );
			
			/* Check if we want to add multiple rows or not */
			if ( typeof mData[0] == "object" )
			{
				for ( var i=0 ; i<mData.length ; i++ )
				{
					iTest = _fnAddData( oSettings, mData[i] );
					if ( iTest == -1 )
					{
						return aiReturn;
					}
					aiReturn.push( iTest );
				}
			}
			else
			{
				iTest = _fnAddData( oSettings, mData );
				if ( iTest == -1 )
				{
					return aiReturn;
				}
				aiReturn.push( iTest );
			}
			
			oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
			
			/* Rebuild the search */
			_fnBuildSearchArray( oSettings, 1 );
			
			if ( bRedraw )
			{
				_fnReDraw( oSettings );
			}
			return aiReturn;
		};
		
		/*
		 * Function: fnDeleteRow
		 * Purpose:  Remove a row for the table
		 * Returns:  array:aReturn - the row that was deleted
		 * Inputs:   int:iIndex - index of aoData to be deleted
		 * Notes:    This function requires a little explanation - we don't actually delete the data
		 *   from aoData - rather we remove it's references from aiDisplayMastr and aiDisplay. This
		 *   in effect prevnts DataTables from drawing it (hence deleting it) - it could be restored
		 *   if you really wanted. The reason for this is that actually removing the aoData object
		 *   would mess up all the subsequent indexes in the display arrays (they could be ajusted - 
		 *   but this appears to do what is required).
		 */
		this.fnDeleteRow = function( iAODataIndex, fnCallBack )
		{
			/* Find settings from table node */
			var oSettings = _fnSettingsFromNode( this[0] );
			var i;
			
			/* Delete from the display master */
			for ( i=0 ; i<oSettings.aiDisplayMaster.length ; i++ )
			{
				if ( oSettings.aiDisplayMaster[i] == iAODataIndex )
				{
					oSettings.aiDisplayMaster.splice( i, 1 );
					break;
				}
			}
			
			/* Delete from the current display index */
			for ( i=0 ; i<oSettings.aiDisplay.length ; i++ )
			{
				if ( oSettings.aiDisplay[i] == iAODataIndex )
				{
					oSettings.aiDisplay.splice( i, 1 );
					break;
				}
			}
			
			/* Rebuild the search */
			_fnBuildSearchArray( oSettings, 1 );
			
			/* If there is a user callback function - call it */
			if ( typeof fnCallBack == "function" )
			{
				fnCallBack.call( this );
			}
			
			/* Check for an 'overflow' they case for dislaying the table */
			if ( oSettings._iDisplayStart >= oSettings.aiDisplay.length )
			{
				oSettings._iDisplayStart -= oSettings._iDisplayLength;
				if ( oSettings._iDisplayStart < 0 )
				{
					oSettings._iDisplayStart = 0;
				}
			}
			
			_fnCalculateEnd( oSettings );
			_fnDraw( oSettings );
			
			return oSettings.aoData[iAODataIndex]._aData.slice();
		};
		
		/*
		 * Function: fnClearTable
		 * Purpose:  Quickly and simply clear a table
		 * Returns:  -
		 * Inputs:   bool:bRedraw - redraw the table or not - default true
		 * Notes:    Thanks to Yekimov Denis for contributing the basis for this function!
		 */
		this.fnClearTable = function( bRedraw )
		{
			/* Find settings from table node */
			var oSettings = _fnSettingsFromNode( this[0] );
			_fnClearTable( oSettings );
			
			if ( typeof bRedraw == 'undefined' || bRedraw )
			{
				_fnDraw( oSettings );
			}
		};
		
		/*
		 * Function: fnOpen
		 * Purpose:  Open a display row (append a row after the row in question)
		 * Returns:  -
		 * Inputs:   node:nTr - the table row to 'open'
		 *           string:sHtml - the HTML to put into the row
		 *           string:sClass - class to give the new cell
		 */
		this.fnOpen = function( nTr, sHtml, sClass )
		{
			/* Find settings from table node */
			var oSettings = _fnSettingsFromNode( this[0] );
			
			/* the old open one if there is one */
			this.fnClose( nTr );
			
			
			var nNewRow = document.createElement("tr");
			var nNewCell = document.createElement("td");
			nNewRow.appendChild( nNewCell );
			nNewCell.className = sClass;
			nNewCell.colSpan = _fnVisbleColumns( oSettings );
			nNewCell.innerHTML = sHtml;
			
			$(nNewRow).insertAfter(nTr);
			
			/* No point in storing the row if using server-side processing since the nParent will be
			 * nuked on a re-draw anyway
			 */
			if ( !oSettings.oFeatures.bServerSide )
			{
				oSettings.aoOpenRows.push( {
					"nTr": nNewRow,
					"nParent": nTr
				} );
			}
		};
		
		/*
		 * Function: fnClose
		 * Purpose:  Close a display row
		 * Returns:  int: 0 (success) or 1 (failed)
		 * Inputs:   node:nTr - the table row to 'close'
		 */
		this.fnClose = function( nTr )
		{
			/* Find settings from table node */
			var oSettings = _fnSettingsFromNode( this[0] );
			
			for ( var i=0 ; i<oSettings.aoOpenRows.length ; i++ )
			{
				if ( oSettings.aoOpenRows[i].nParent == nTr )
				{
					var nTrParent = oSettings.aoOpenRows[i].nTr.parentNode;
					if ( nTrParent )
					{
						/* Remove it if it is currently on display */
						nTrParent.removeChild( oSettings.aoOpenRows[i].nTr );
					}
					oSettings.aoOpenRows.splice( i, 1 );
					return 0;
				}
			}
			return 1;
		};
		
		/*
		 * Function: fnGetData
		 * Purpose:  Return an array with the data which is used to make up the table
		 * Returns:  array array string: 2d data array ([row][column]) or array string: 1d data array
		 *           or
		 *           array string (if iRow specified)
		 * Inputs:   int:iRow - optional - if present then the array returned will be the data for
		 *             the row with the index 'iRow'
		 */
		this.fnGetData = function( iRow )
		{
			var oSettings = _fnSettingsFromNode( this[0] );
			
			if ( typeof iRow != 'undefined' )
			{
				return oSettings.aoData[iRow]._aData;
			}
			return _fnGetDataMaster( oSettings );
		};
		
		/*
		 * Function: fnGetNodes
		 * Purpose:  Return an array with the TR nodes used for drawing the table
		 * Returns:  array node: TR elements
		 *           or
		 *           node (if iRow specified)
		 * Inputs:   int:iRow - optional - if present then the array returned will be the node for
		 *             the row with the index 'iRow'
		 */
		this.fnGetNodes = function( iRow )
		{
			var oSettings = _fnSettingsFromNode( this[0] );
			
			if ( typeof iRow != 'undefined' )
			{
				return oSettings.aoData[iRow].nTr;
			}
			return _fnGetTrNodes( oSettings );
		};
		
		/*
		 * Function: fnGetPosition
		 * Purpose:  Get the array indexes of a particular cell from it's DOM element
		 * Returns:  int: - row index, or array[ int, int ]: - row index and column index
		 * Inputs:   node:nNode - this can either be a TR or a TD in the table, the return is
		 *             dependent on this input
		 */
		this.fnGetPosition = function( nNode )
		{
			var oSettings = _fnSettingsFromNode( this[0] );
			var i;
			
			if ( nNode.nodeName == "TR" )
			{
				for ( i=0 ; i<oSettings.aoData.length ; i++ )
				{
					if ( oSettings.aoData[i].nTr == nNode )
					{
						return i;
					}
				}
			}
			else if ( nNode.nodeName == "TD" )
			{
				for ( i=0 ; i<oSettings.aoData.length ; i++ )
				{
					var iCorrector = 0;
					for ( var j=0 ; j<oSettings.aoColumns.length ; j++ )
					{
						if ( oSettings.aoColumns[j].bVisible )
						{
							if ( oSettings.aoData[i].nTr.getElementsByTagName('td')[j-iCorrector] == nNode )
							{
								return [ i, j-iCorrector, j ];
							}
						}
						else
						{
							iCorrector++;
						}
					}
				}
			}
			return null;
		};
		
		/*
		 * Function: fnUpdate
		 * Purpose:  Update a table cell or row
		 * Returns:  int: 0 okay, 1 error
		 * Inputs:   array string 'or' string:mData - data to update the cell/row with
		 *           int:iRow - the row (from aoData) to update
		 *           int:iColumn - the column to update
		 *           bool:bRedraw - redraw the table or not - default true
		 */
		this.fnUpdate = function( mData, iRow, iColumn, bRedraw )
		{
			var oSettings = _fnSettingsFromNode( this[0] );
			var iVisibleColumn;
			var sDisplay;
			if ( typeof bRedraw == 'undefined' )
			{
				bRedraw = true;
			}
			
			if ( typeof mData != 'object' )
			{
				sDisplay = mData;
				oSettings.aoData[iRow]._aData[iColumn] = sDisplay;
				
				if ( oSettings.aoColumns[iColumn].fnRender !== null )
				{
					sDisplay = oSettings.aoColumns[iColumn].fnRender( {
						"iDataRow": iRow,
						"iDataColumn": iColumn,
						"aData": oSettings.aoData[iRow]._aData
					} );
					
					if ( oSettings.aoColumns[iColumn].bUseRendered )
					{
						oSettings.aoData[iRow]._aData[iColumn] = sDisplay;
					}
				}
				
				iVisibleColumn = _fnColumnIndexToVisible( oSettings, iColumn );
				if ( iVisibleColumn !== null )
				{
					oSettings.aoData[iRow].nTr.getElementsByTagName('td')[iVisibleColumn].innerHTML = 
						sDisplay;
				}
			}
			else
			{
				if ( mData.length != oSettings.aoColumns.length )
				{
					alert( 'Warning: An array passed to fnUpdate must have the same number of columns as '+
						'the table in question - in this case '+oSettings.aoColumns.length );
					return 1;
				}
				
				for ( var i=0 ; i<mData.length ; i++ )
				{
					sDisplay = mData[i];
					oSettings.aoData[iRow]._aData[i] = sDisplay;
					
					if ( oSettings.aoColumns[i].fnRender !== null )
					{
						sDisplay = oSettings.aoColumns[i].fnRender( {
							"iDataRow": iRow,
							"iDataColumn": i,
							"aData": oSettings.aoData[iRow]._aData
						} );
						
						if ( oSettings.aoColumns[i].bUseRendered )
						{
							oSettings.aoData[iRow]._aData[i] = sDisplay;
						}
					}
					
					iVisibleColumn = _fnColumnIndexToVisible( oSettings, i );
					if ( iVisibleColumn !== null )
					{
						oSettings.aoData[iRow].nTr.getElementsByTagName('td')[iVisibleColumn].innerHTML = 
							sDisplay;
					}
				}
			}
			
			/* Update the search array */
			_fnBuildSearchArray( oSettings, 1 );
			
			/* Redraw the table */
			if ( bRedraw )
			{
				_fnReDraw( oSettings );
			}
			return 0;
		};
		
		
		/*
		 * Function: fnShowColoumn
		 * Purpose:  Show a particular column
		 * Returns:  -
		 * Inputs:   int:iCol - the column whose display should be changed
		 *           bool:bShow - show (true) or hide (false) the column
		 */
		this.fnSetColumnVis = function ( iCol, bShow )
		{
			var oSettings = _fnSettingsFromNode( this[0] );
			var i, iLen;
			var iColumns = oSettings.aoColumns.length;
			var nTd;
			
			/* No point in doing anything if we are requesting what is already true */
			if ( oSettings.aoColumns[iCol].bVisible == bShow )
			{
				return;
			}
			
			var nTrHead = $('thead tr', oSettings.nTable)[0];
			var nTrFoot = $('tfoot tr', oSettings.nTable)[0];
			var anTheadTh = [];
			var anTfootTh = [];
			for ( i=0 ; i<iColumns ; i++ )
			{
				anTheadTh.push( oSettings.aoColumns[i].nTh );
				anTfootTh.push( oSettings.aoColumns[i].nTf );
			}
			
			/* Show the column */
			if ( bShow )
			{
				var iInsert = 0;
				for ( i=0 ; i<iCol ; i++ )
				{
					if ( oSettings.aoColumns[i].bVisible )
					{
						iInsert++;
					}
				}
				
				/* Need to decide if we should use appendChild or insertBefore */
				if ( iInsert >= _fnVisbleColumns( oSettings ) )
				{
					nTrHead.appendChild( anTheadTh[iCol] );
					if ( nTrFoot )
					{
						nTrFoot.appendChild( anTfootTh[iCol] );
					}
					
					for ( i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ )
					{
						nTd = oSettings.aoData[i]._anHidden[iCol];
						oSettings.aoData[i].nTr.appendChild( nTd );
					}
				}
				else
				{
					/* Which coloumn should we be inserting before? */
					var iBefore;
					for ( i=iCol ; i<iColumns ; i++ )
					{
						iBefore = _fnColumnIndexToVisible( oSettings, i );
						if ( iBefore !== null )
						{
							break;
						}
					}
					
					nTrHead.insertBefore( anTheadTh[iCol], nTrHead.getElementsByTagName('th')[iBefore] );
					if ( nTrFoot )
					{
						nTrFoot.insertBefore( anTfootTh[iCol], nTrFoot.getElementsByTagName('th')[iBefore] );
					}
					
					for ( i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ )
					{
						nTd = oSettings.aoData[i]._anHidden[iCol];
						oSettings.aoData[i].nTr.insertBefore( nTd, oSettings.aoData[i].nTr.getElementsByTagName('td')[iBefore] );
					}
				}
				
				oSettings.aoColumns[iCol].bVisible = true;
			}
			else
			{
				/* Remove a column from display */
				nTrHead.removeChild( anTheadTh[iCol] );
				if ( nTrFoot )
				{
					nTrFoot.removeChild( anTfootTh[iCol] );
				}
				
				var iVisCol = _fnColumnIndexToVisible(oSettings, iCol);
				for ( i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ )
				{
					nTd = oSettings.aoData[i].nTr.getElementsByTagName('td')[ iVisCol ];
					oSettings.aoData[i]._anHidden[iCol] = nTd;
					nTd.parentNode.removeChild( nTd );
				}
				
				oSettings.aoColumns[iCol].bVisible = false;
			}
		};
		
		
		/*
		 * Plugin API functions
		 * 
		 * This call will add the functions which are defined in $.fn.dataTableExt.oApi to the
		 * DataTables object, providing a rather nice way to allow plug-in API functions. Note that
		 * this is done here, so that API function can actually override the built in API functions if
		 * required for a particular purpose. Also the local bApi flag is used for allowing DataTables
		 * to decide (later on) if it should expose it's private functions or not.
		 */
		
		/*
		 * Function: _fnExternApiFunc
		 * Purpose:  Create a wrapper function for exporting an internal func to an external API func
		 * Returns:  function: - wrapped function
		 * Inputs:   string:sFunc - API function name
		 */
		function _fnExternApiFunc (sFunc)
		{
			return function() {
					var aArgs = [_fnSettingsFromNode(this[0])].concat( Array.prototype.slice.call(arguments) );
					return $.fn.dataTableExt.oApi[sFunc].apply( this, aArgs );
				};
		}
		
		var bApi = false;
		for ( var sFunc in $.fn.dataTableExt.oApi )
		{
			if ( sFunc )
			{
				/*
				 * Function: anon
				 * Purpose:  Wrap the plug-in API functions in order to provide the settings as 1st arg 
				 *   and execute in this scope
				 * Returns:  -
				 * Inputs:   -
				 */
				this[sFunc] = _fnExternApiFunc(sFunc);
				bApi = true;
			}
		}
		
		
		
		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
		 * Local functions
		 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
		
		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
		 * Initalisation
		 */
		
		/*
		 * Function: _fnInitalise
		 * Purpose:  Draw the table for the first time, adding all required features
		 * Returns:  -
		 * Inputs:   object:oSettings - dataTables settings object
		 */
		function _fnInitalise ( oSettings )
		{
			/* Ensure that the table data is fully initialised */
			if ( oSettings.bInitialised === false )
			{
				setTimeout( function(){ _fnInitalise( oSettings ); }, 200 );
				return;
			}
			
			/* Show the display HTML options */
			_fnAddOptionsHtml( oSettings );
			
			/* Draw the headers for the table */
			_fnDrawHead( oSettings );
			
			/* If there is default sorting required - let's do it. The sort function
			 * will do the drawing for us. Otherwise we draw the table
			 */
			if ( oSettings.oFeatures.bSort )
			{
				_fnSort( oSettings, false );
				/*
				 * Add the sorting classes to the header and the body (if needed).
				 * Reason for doing it here after the first draw is to stop classes being applied to the
				 * 'static' table.
				 */
				_fnSortingClasses( oSettings );
			}
			else
			{
				oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
				_fnCalculateEnd( oSettings );
				_fnDraw( oSettings );
			}
			
			/* if there is an ajax source */
			if ( oSettings.sAjaxSource !== null && !oSettings.oFeatures.bServerSide )
			{
				_fnProcessingDisplay( oSettings, true );
				
				$.getJSON( oSettings.sAjaxSource, null, function(json) {
					/* Got the data - add it to the table */
					for ( var i=0 ; i<json.aaData.length ; i++ )
					{
						_fnAddData( oSettings, json.aaData[i] );
					}
					
					if ( oSettings.oFeatures.bSort )
					{
						_fnSort( oSettings );
					}
					else
					{
						oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
						_fnCalculateEnd( oSettings );
						_fnDraw( oSettings );
					}
					_fnProcessingDisplay( oSettings, false );
					
					/* Run the init callback if there is one */
					if ( typeof oSettings.fnInitComplete == 'function' )
					{
						oSettings.fnInitComplete( oSettings );
					}
				} );
				return;
			}
			
			/* Run the init callback if there is one */
			if ( typeof oSettings.fnInitComplete == 'function' )
			{
				oSettings.fnInitComplete( oSettings );
			}
		}
		
		/*
		 * Function: _fnLanguageProcess
		 * Purpose:  Copy language variables from remote object to a local one
		 * Returns:  -
		 * Inputs:   object:oSettings - dataTables settings object
		 *           object:oLanguage - Language information
		 *           bool:bInit - init once complete
		 */
		function _fnLanguageProcess( oSettings, oLanguage, bInit )
		{
			_fnMap( oSettings.oLanguage, oLanguage, 'sProcessing' );
			_fnMap( oSettings.oLanguage, oLanguage, 'sLengthMenu' );
			_fnMap( oSettings.oLanguage, oLanguage, 'sZeroRecords' );
			_fnMap( oSettings.oLanguage, oLanguage, 'sInfo' );
			_fnMap( oSettings.oLanguage, oLanguage, 'sInfoEmpty' );
			_fnMap( oSettings.oLanguage, oLanguage, 'sInfoFiltered' );
			_fnMap( oSettings.oLanguage, oLanguage, 'sInfoPostFix' );
			_fnMap( oSettings.oLanguage, oLanguage, 'sSearch' );
			
			if ( typeof oLanguage.oPaginate != 'undefined' )
			{
				_fnMap( oSettings.oLanguage.oPaginate, oLanguage.oPaginate, 'sFirst' );
				_fnMap( oSettings.oLanguage.oPaginate, oLanguage.oPaginate, 'sPrevious' );
				_fnMap( oSettings.oLanguage.oPaginate, oLanguage.oPaginate, 'sNext' );
				_fnMap( oSettings.oLanguage.oPaginate, oLanguage.oPaginate, 'sLast' );
			}
			
			if ( bInit )
			{
				_fnInitalise( oSettings );
			}
		}
		
		/*
		 * Function: _fnAddColumn
		 * Purpose:  Add a column to the list used for the table
		 * Returns:  -
		 * Inputs:   object:oSettings - dataTables settings object
		 *           object:oOptions - object with sType, bVisible and bSearchable
		 *           node:nTh - the th element for this column
		 * Notes:    All options in enter column can be over-ridden by the user
		 *   initialisation of dataTables
		 */
		function _fnAddColumn( oSettings, oOptions, nTh )
		{
			oSettings.aoColumns[ oSettings.aoColumns.length++ ] = {
				"sType": null,
				"_bAutoType": true,
				"bVisible": true,
				"bSearchable": true,
				"bSortable": true,
				"sTitle": null,
				"sName": '',
				"sWidth": null,
				"sClass": null,
				"fnRender": null,
				"bUseRendered": true,
				"iDataSort": oSettings.aoColumns.length-1,
				"nTh": nTh ? nTh : document.createElement('th'),
				"nTf": null
			};
			
			/* User specified column options */
			var iLength = oSettings.aoColumns.length-1;
			if ( typeof oOptions != 'undefined' && oOptions !== null )
			{
				var oCol = oSettings.aoColumns[ iLength ];
				
				if ( typeof oOptions.sType != 'undefined' )
				{
					oCol.sType = oOptions.sType;
					oCol._bAutoType = false;
				}
				
				_fnMap( oCol, oOptions, "bVisible" );
				_fnMap( oCol, oOptions, "bSearchable" );
				_fnMap( oCol, oOptions, "bSortable" );
				_fnMap( oCol, oOptions, "sTitle" );
				_fnMap( oCol, oOptions, "sName" );
				_fnMap( oCol, oOptions, "sWidth" );
				_fnMap( oCol, oOptions, "sClass" );
				_fnMap( oCol, oOptions, "fnRender" );
				_fnMap( oCol, oOptions, "bUseRendered" );
				_fnMap( oCol, oOptions, "iDataSort" );
			}
			
			/* Add a column specific filter */
			if ( typeof oSettings.aoPreSearchCols[ iLength ] == 'undefined' )
			{
				oSettings.aoPreSearchCols[ iLength ] = {
					"sSearch": "",
					"bEscapeRegex": true
				};
			}
		}
		
		/*
		 * Function: _fnAddData
		 * Purpose:  Add a data array to the table, creating DOM node etc
		 * Returns:  int: - >=0 if successful (index of new aoData entry), -1 if failed
		 * Inputs:   object:oSettings - dataTables settings object
		 *           array:aData - data array to be added
		 */
		function _fnAddData ( oSettings, aData )
		{
			/* Sanity check the length of the new array */
			if ( aData.length != oSettings.aoColumns.length )
			{
				return -1;
			}
			
			/* Create the object for storing information about this new row */
			var iThisIndex = oSettings.aoData.length;
			oSettings.aoData.push( {
				"_iId": oSettings.iNextId++,
				"_aData": aData.slice(),
				"nTr": document.createElement('tr'),
				"_anHidden": []
			} );
			
			/* Create the cells */
			var nTd;
			for ( var i=0 ; i<aData.length ; i++ )
			{
				nTd = document.createElement('td');
				
				if ( typeof oSettings.aoColumns[i].fnRender == 'function' )
				{
					var sRendered = oSettings.aoColumns[i].fnRender( {
							"iDataRow": iThisIndex,
							"iDataColumn": i,
							"aData": aData
						} );
					nTd.innerHTML = sRendered;
					if ( oSettings.aoColumns[i].bUseRendered )
					{
						/* Use the rendered data for filtering/sorting */
						oSettings.aoData[iThisIndex]._aData[i] = sRendered;
					}
				}
				else
				{
					nTd.innerHTML = aData[i];
				}
				
				if ( oSettings.aoColumns[i].sClass !== null )
				{
					nTd.className = oSettings.aoColumns[i].sClass;
				}
				
				/* See if we should auto-detect the column type */
				if ( oSettings.aoColumns[i]._bAutoType && oSettings.aoColumns[i].sType != 'string' )
				{
					/* Attempt to auto detect the type - same as _fnGatherData() */
					if ( oSettings.aoColumns[i].sType === null )
					{
						oSettings.aoColumns[i].sType = _fnDetectType( aData[i] );
					}
					else if ( oSettings.aoColumns[i].sType == "date" || 
					          oSettings.aoColumns[i].sType == "numeric" )
					{
						oSettings.aoColumns[i].sType = _fnDetectType( aData[i] );
					}
				}
					
				if ( oSettings.aoColumns[i].bVisible )
				{
					oSettings.aoData[iThisIndex].nTr.appendChild( nTd );
				}
				else
				{
					oSettings.aoData[iThisIndex]._anHidden[i] = nTd;
				}
			}
			
			/* Add to the display array */
			oSettings.aiDisplayMaster.push( iThisIndex );
			return iThisIndex;
		}
		
		/*
		 * Function: _fnGatherData
		 * Purpose:  Read in the data from the target table
		 * Returns:  -
		 * Inputs:   object:oSettings - dataTables settings object
		 */
		function _fnGatherData( oSettings )
		{
			var nDataNodes;
			var iDataLength = $('tbody:eq(0)>tr').length;
			var iLoop;
			var i, j;
			
			/*
			 * Process by row first
			 * Add the data object for the whole table - storing the tr node. Note - no point in getting
			 * DOM based data if we are going to go and replace it with Ajax source data.
			 */
			if ( oSettings.sAjaxSource === null )
			{
				$('tbody:eq(0)>tr', oSettings.nTable).each( function() {
					var iThisIndex = oSettings.aoData.length;
					oSettings.aoData.push( {
						"_iId": oSettings.iNextId++,
						"_aData": [],
						"nTr": this,
						"_anHidden": []
					} );
					
					oSettings.aiDisplayMaster.push( iThisIndex );
					
					/* Add the data for this column */
					var aLocalData = oSettings.aoData[iThisIndex]._aData;
					$('td', this).each( function( i ) {
						aLocalData[i] = this.innerHTML;
					} );
				} );
			}
			
			/*
			 * Now process by column
			 */
			var iCorrector = 0;
			for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
			{
				/* Get the title of the column - unless there is a user set one */
				if ( oSettings.aoColumns[i].sTitle === null )
				{
					oSettings.aoColumns[i].sTitle = oSettings.aoColumns[i].nTh.innerHTML;
				}
				
				var bAutoType = oSettings.aoColumns[i]._bAutoType;
				var bRender = typeof oSettings.aoColumns[i].fnRender == 'function';
				var bClass = oSettings.aoColumns[i].sClass !== null;
				var bVisible = oSettings.aoColumns[i].bVisible;
				
				/* A single loop to rule them all (and be more efficient) */
				if ( bAutoType || bRender || bClass || !bVisible )
				{
					iLoop = oSettings.aoData.length;
					for ( j=0 ; j<iLoop ; j++ )
					{
						var nCellNode = oSettings.aoData[j].nTr.getElementsByTagName('td')[ i-iCorrector ];
						
						if ( bAutoType )
						{
							if ( oSettings.aoColumns[i].sType === null )
							{
								oSettings.aoColumns[i].sType = _fnDetectType( oSettings.aoData[j]._aData[i] );
							}
							else if ( oSettings.aoColumns[i].sType == "date" || 
							          oSettings.aoColumns[i].sType == "numeric" )
							{
								/* If type is date or numeric - ensure that all collected data
								 * in the column is of the same type
								 */
								oSettings.aoColumns[i].sType = _fnDetectType( oSettings.aoData[j]._aData[i] );
							}
							/* The else would be 'type = string' we don't want to do anything
							 * if that is the case
							 */
						}
						
						if ( bRender )
						{
							var sRendered = oSettings.aoColumns[i].fnRender( {
									"iDataRow": j,
									"iDataColumn": i,
									"aData": oSettings.aoData[j]._aData
								} );
							nCellNode.innerHTML = sRendered;
							if ( oSettings.aoColumns[i].bUseRendered )
							{
								/* Use the rendered data for filtering/sorting */
								oSettings.aoData[j]._aData[i] = sRendered;
							}
						}
						
						if ( bClass )
						{
							nCellNode.className += ' '+oSettings.aoColumns[i].sClass;
						}
						
						if ( !bVisible )
						{
							oSettings.aoData[j]._anHidden[i] = nCellNode;
							nCellNode.parentNode.removeChild( nCellNode );
						}
					}
					
					/* Keep an index corrector for the next loop */
					if ( !bVisible )
					{
						iCorrector++;
					}
				}
			}	
		}
		
		
		
		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
		 * Drawing functions
		 */
		
		/*
		 * Function: _fnDrawHead
		 * Purpose:  Create the HTML header for the table
		 * Returns:  -
		 * Inputs:   object:oSettings - dataTables settings object
		 */
		function _fnDrawHead( oSettings )
		{
			var i, nTh;
			var iThs = oSettings.nTable.getElementsByTagName('thead')[0].getElementsByTagName('th').length;
			var iCorrector = 0;
			
			/* If there is a header in place - then use it - otherwise it's going to get nuked... */
			if ( iThs !== 0 )
			{
				/* We've got a thead from the DOM, so remove hidden columns and apply width to vis cols */
				for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
				{
					//oSettings.aoColumns[i].nTh = nThs[i];
					nTh = oSettings.aoColumns[i].nTh;
					
					if ( oSettings.aoColumns[i].bVisible )
					{
						/* Set width */
						if ( oSettings.aoColumns[i].sWidth !== null )
						{
							nTh.style.width = oSettings.aoColumns[i].sWidth;
						}
						
						/* Set the title of the column if it is user defined (not what was auto detected) */
						if ( oSettings.aoColumns[i].sTitle != nTh.innerHTML )
						{
							nTh.innerHTML = oSettings.aoColumns[i].sTitle;
						}
					}
					else
					{
						nTh.parentNode.removeChild( nTh );
						iCorrector++;
					}
				}
			}
			else
			{
				/* We don't have a header in the DOM - so we are going to have to create one */
				var nTr = document.createElement( "tr" );
				
				for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
				{
					if ( oSettings.aoColumns[i].bVisible )
					{
						nTh = oSettings.aoColumns[i].nTh;
						
						if ( oSettings.aoColumns[i].sClass !== null )
						{
							nTh.className = oSettings.aoColumns[i].sClass;
						}
						
						if ( oSettings.aoColumns[i].sWidth !== null )
						{
							nTh.style.width = oSettings.aoColumns[i].sWidth;
						}
						
						nTh.innerHTML = oSettings.aoColumns[i].sTitle;
						nTr.appendChild( nTh );
					}
				}
				$('thead', oSettings.nTable).html( '' )[0].appendChild( nTr );
			}
			
			
			/* Add sort listener */
			if ( oSettings.oFeatures.bSort )
			{
				for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
				{
					$(oSettings.aoColumns[i].nTh).click( function (e) {
						var iDataIndex;
						/* Find which column we are sorting on - can't use index() due to colspan etc */
						for ( var i=0 ; i<oSettings.aoColumns.length ; i++ )
						{
							if ( oSettings.aoColumns[i].nTh == this )
							{
								iDataIndex = i;
								break;
							}
						}
						
						/* If the column is not sortable - don't to anything */
						if ( oSettings.aoColumns[iDataIndex].bSortable === false )
						{
							return;
						}
						
						/*
						 * This is a little bit odd I admit... I declare a temporary function inside the scope of
						 * _fnDrawHead and the click handler in order that the code presented here can be used 
						 * twice - once for when bProcessing is enabled, and another time for when it is 
						 * disabled, as we need to perform slightly different actions.
						 *   Basically the issue here is that the Javascript engine in modern browsers don't 
						 * appear to allow the rendering engine to update the display while it is still excuting
						 * it's thread (well - it does but only after long intervals). This means that the 
						 * 'processing' display doesn't appear for a table sort. To break the js thread up a bit
						 * I force an execution break by using setTimeout - but this breaks the expected 
						 * thread continuation for the end-developer's point of view (their code would execute
						 * too early), so we on;y do it when we absolutely have to.
						 */
						fnInnerSorting = function () {
							if ( e.shiftKey )
							{
								/* If the shift key is pressed then we are multipe column sorting */
								var bFound = false;
								for ( var i=0 ; i<oSettings.aaSorting.length ; i++ )
								{
									if ( oSettings.aaSorting[i][0] == iDataIndex )
									{
										if ( oSettings.aaSorting[i][1] == "asc" )
										{
											oSettings.aaSorting[i][1] = "desc";
										}
										else
										{
											oSettings.aaSorting.splice( i, 1 );
										}
										bFound = true;
										break;
									}
								}
								
								if ( bFound === false )
								{
									oSettings.aaSorting.push( [ iDataIndex, "asc" ] );
								}
							}
							else
							{
								/* If no shift key then single column sort */
								if ( oSettings.aaSorting.length == 1 && oSettings.aaSorting[0][0] == iDataIndex )
								{
									oSettings.aaSorting[0][1] = oSettings.aaSorting[0][1]=="asc" ? "desc" : "asc";
								}
								else
								{
									oSettings.aaSorting.splice( 0, oSettings.aaSorting.length );
									oSettings.aaSorting.push( [ iDataIndex, "asc" ] );
								}
							}
							
							/* Run the sort */
							_fnSort( oSettings );
						}; /* /fnInnerSorting */
						
						if ( !oSettings.oFeatures.bProcessing )
						{
							fnInnerSorting();
						}
						else
						{
							_fnProcessingDisplay( oSettings, true );
							setTimeout( function() {
								fnInnerSorting();
								_fnProcessingDisplay( oSettings, false );
							}, 0 );
						}
					} ); /* /click */
				} /* For each column */
				
				/* Take the brutal approach to cancelling text selection due to the shift key */
				$('thead th', oSettings.nTable).mousedown( function () {
					this.onselectstart = function() { return false; };
					return false;
				} );
			} /* /if feature sort */
			
			/* Set an absolute width for the table such that pagination doesn't
			 * cause the table to resize
			 */
			if ( oSettings.oFeatures.bAutoWidth )
			{
				oSettings.nTable.style.width = oSettings.nTable.offsetWidth+"px";
			}
			
			/* Cache the footer elements */
			var nTfoot = oSettings.nTable.getElementsByTagName('tfoot');
			if ( nTfoot.length !== 0 )
			{
				nTfs = nTfoot[0].getElementsByTagName('th');
				for ( i=0, iLen=nTfs.length ; i<iLen ; i++ )
				{
					oSettings.aoColumns[i].nTf = nTfs[i];
				}
			}
		}
		
		/*
		 * Function: _fnDraw
		 * Purpose:  Insert the required TR nodes into the table for display
		 * Returns:  -
		 * Inputs:   object:oSettings - dataTables settings object
		 */
		function _fnDraw( oSettings )
		{
			var i;
			var anRows = [];
			var iRowCount = 0;
			var iStrips = oSettings.asStripClasses.length;
			var iOpenRows = oSettings.aoOpenRows.length;
			
			/* If we are dealing with Ajax - do it here */
			if ( oSettings.oFeatures.bServerSide && 
			     !_fnAjaxUpdate( oSettings ) )
			{
				return;
			}
			
			if ( oSettings.aiDisplay.length !== 0 )
			{
				var iStart = oSettings._iDisplayStart;
				var iEnd = oSettings._iDisplayEnd;
				
				if ( oSettings.oFeatures.bServerSide )
				{
					iStart = 0;
					iEnd = oSettings.aoData.length;
				}
				
				for ( var j=iStart ; j<iEnd ; j++ )
				{
					var nRow = oSettings.aoData[ oSettings.aiDisplay[j] ].nTr;
					
					/* Remove any old stripping classes and then add the new one */
					$(nRow).removeClass( oSettings.asStripClasses.join(' ') );
					$(nRow).addClass( oSettings.asStripClasses[ iRowCount % iStrips ] );
					
					/* Custom row callback function - might want to manipule the row */
					if ( typeof oSettings.fnRowCallback == "function" )
					{
						nRow = oSettings.fnRowCallback( nRow, 
							oSettings.aoData[ oSettings.aiDisplay[j] ]._aData, iRowCount, j );
					}
					
					anRows.push( nRow );
					iRowCount++;
					
					/* If there is an open row - and it is attached to this parent - attach it on redraw */
					if ( iOpenRows !== 0 )
					{
						for ( var k=0 ; k<iOpenRows ; k++ )
						{
							if ( nRow == oSettings.aoOpenRows[k].nParent )
							{
								anRows.push( oSettings.aoOpenRows[k].nTr );
							}
						}
					}
				}
			}
			else
			{
				/* Table is empty - create a row with an empty message in it */
				anRows[ 0 ] = document.createElement( 'tr' );
				
				if ( typeof oSettings.asStripClasses[0] != 'undefined' )
				{
					anRows[ 0 ].className = oSettings.asStripClasses[0];
				}
				
				var nTd = document.createElement( 'td' );
				nTd.setAttribute( 'valign', "top" );
				nTd.colSpan = oSettings.aoColumns.length;
				nTd.className = 'dataTables_empty';
				nTd.innerHTML = oSettings.oLanguage.sZeroRecords;
				
				anRows[ iRowCount ].appendChild( nTd );
			}
			
			/* Callback the header and footer custom funcation if there is one */
			if ( typeof oSettings.fnHeaderCallback == 'function' )
			{
				oSettings.fnHeaderCallback( $('thead tr', oSettings.nTable)[0], 
					_fnGetDataMaster( oSettings ), oSettings._iDisplayStart, oSettings._iDisplayEnd,
					oSettings.aiDisplay );
			}
			
			if ( typeof oSettings.fnFooterCallback == 'function' )
			{
				oSettings.fnFooterCallback( $('tfoot tr', oSettings.nTable)[0], 
					_fnGetDataMaster( oSettings ), oSettings._iDisplayStart, oSettings._iDisplayEnd,
					oSettings.aiDisplay );
			}
			
			/* 
			 * Need to remove any old row from the display - note we can't just empty the tbody using
			 * .html('') since this will unbind the jQuery event handlers (even although the node still
			 * exists!) - note the initially odd ':eq(0)>tr' expression. This basically ensures that we
			 * only get tr elements of the tbody that the data table has been initialised on. If there
			 * are nested tables then we don't want to remove those elements.
			 */
			var nTrs = $('tbody:eq(0)>tr', oSettings.nTable);
			for ( i=0 ; i<nTrs.length ; i++ )
			{
				nTrs[i].parentNode.removeChild( nTrs[i] );
			}
			
			/* Put the draw table into the dom */
			var nBody = $('tbody:eq(0)', oSettings.nTable);
			for ( i=0 ; i<anRows.length ; i++ )
			{
				nBody[0].appendChild( anRows[i] );
			}
			
			/* Update the pagination display buttons */
			if ( oSettings.oFeatures.bPaginate )
			{
				$.fn.dataTableExt.oPagination[ oSettings.sPaginationType ].fnUpdate( oSettings, function( oSettings ) {
					_fnCalculateEnd( oSettings );
					_fnDraw( oSettings );
				} );
			}
			
			/* Show information about the table */
			if ( oSettings.oFeatures.bInfo )
			{
				/* Update the information */
				if ( oSettings.fnRecordsDisplay() === 0 && 
					   oSettings.fnRecordsDisplay() == oSettings.fnRecordsTotal() )
				{
					oSettings.nInfo.innerHTML = 
						oSettings.oLanguage.sInfoEmpty+ oSettings.oLanguage.sInfoPostFix;
				}
				else if ( oSettings.fnRecordsDisplay() === 0 )
				{
					oSettings.nInfo.innerHTML = oSettings.oLanguage.sInfoEmpty +' '+ 
						oSettings.oLanguage.sInfoFiltered.replace('_MAX_', 
							oSettings.fnRecordsTotal())+ oSettings.oLanguage.sInfoPostFix;
				}
				else if ( oSettings.fnRecordsDisplay() == oSettings.fnRecordsTotal() )
				{
					oSettings.nInfo.innerHTML = 
						oSettings.oLanguage.sInfo.
							replace('_START_',oSettings._iDisplayStart+1).
							replace('_END_',oSettings.fnDisplayEnd()).
							replace('_TOTAL_',oSettings.fnRecordsDisplay())+ 
						oSettings.oLanguage.sInfoPostFix;
				}
				else
				{
					oSettings.nInfo.innerHTML = 
						oSettings.oLanguage.sInfo.
							replace('_START_',oSettings._iDisplayStart+1).
							replace('_END_',oSettings.fnDisplayEnd()).
							replace('_TOTAL_',oSettings.fnRecordsDisplay()) +' '+ 
						oSettings.oLanguage.sInfoFiltered.replace('_MAX_', oSettings.fnRecordsTotal())+ 
						oSettings.oLanguage.sInfoPostFix;
				}
			}
			
			/* Alter the sorting classes to take account of the changes */
			if ( oSettings.oFeatures.bServerSide && oSettings.oFeatures.bSort )
			{
				_fnSortingClasses( oSettings );
			}
			
			/* Save the table state on each draw */
			_fnSaveState( oSettings );
			
			/* Drawing is finished - call the callback if there is one */
			if ( typeof oSettings.fnDrawCallback == 'function' )
			{
				oSettings.fnDrawCallback( oSettings );
			}
		}
		
		/*
		 * Function: _fnReDraw
		 * Purpose:  Redraw the table - taking account of the various features which are enabled
		 * Returns:  -
		 * Inputs:   object:oSettings - dataTables settings object
		 */
		function _fnReDraw( oSettings )
		{
			if ( oSettings.oFeatures.bSort )
			{
				/* Sorting will refilter and draw for us */
				_fnSort( oSettings, oSettings.oPreviousSearch );
			}
			else if ( oSettings.oFeatures.bFilter )
			{
				/* Filtering will redraw for us */
				_fnFilterComplete( oSettings, oSettings.oPreviousSearch );
			}
			else
			{
				_fnCalculateEnd( oSettings );
				_fnDraw( oSettings );
			}
		}
		
		/*
		 * Function: _fnAjaxUpdate
		 * Purpose:  Update the table using an Ajax call
		 * Returns:  bool: block the table drawing or not
		 * Inputs:   object:oSettings - dataTables settings object
		 */
		function _fnAjaxUpdate( oSettings )
		{
			if ( oSettings.bAjaxDataGet )
			{
				_fnProcessingDisplay( oSettings, true );
				var iColumns = oSettings.aoColumns.length;
				var aoData = [];
				var i;
				
				/* Paging and general */
				aoData.push( { "name": "iColumns",       "value": iColumns } );
				aoData.push( { "name": "sColumns",       "value": _fnColumnOrdering(oSettings) } );
				aoData.push( { "name": "iDisplayLength", "value": oSettings._iDisplayLength } );
				aoData.push( { "name": "iDisplayStart",  "value": oSettings._iDisplayStart } );
				
				/* Filtering */
				aoData.push( { "name": "sSearch",        "value": oSettings.oPreviousSearch.sSearch } );
				aoData.push( { "name": "bEscapeRegex",   "value": oSettings.oPreviousSearch.bEscapeRegex } );
				for ( i=0 ; i<iColumns ; i++ )
				{
					aoData.push( { "name": "sSearch_"+i,      "value": oSettings.aoPreSearchCols[i].sSearch } );
					aoData.push( { "name": "bEscapeRegex_"+i, "value": oSettings.aoPreSearchCols[i].bEscapeRegex } );
				}
				
				/* Sorting */
				var iFixed = oSettings.aaSortingFixed !== null ? oSettings.aaSortingFixed.length : 0;
				var iUser = oSettings.aaSorting.length;
				aoData.push( { "name": "iSortingCols",   "value": iFixed+iUser } );
				for ( i=0 ; i<iFixed ; i++ )
				{
					aoData.push( { "name": "iSortCol_"+i,  "value": oSettings.aaSortingFixed[i][0] } );
					aoData.push( { "name": "iSortDir_"+i,  "value": oSettings.aaSortingFixed[i][1] } );
				}
				
				for ( i=0 ; i<iUser ; i++ )
				{
					aoData.push( { "name": "iSortCol_"+(i+iFixed),  "value": oSettings.aaSorting[i][0] } );
					aoData.push( { "name": "iSortDir_"+(i+iFixed),  "value": oSettings.aaSorting[i][1] } );
				}
				
				oSettings.fnServerData( oSettings.sAjaxSource, aoData, function(json) {
					_fnAjaxUpdateDraw( oSettings, json );
				} );
				return false;
			}
			else
			{
				return true;
			}
		}
		
		/*
		 * Function: _fnAjaxUpdateDraw
		 * Purpose:  Data the data from the server (nuking the old) and redraw the table
		 * Returns:  -
		 * Inputs:   object:oSettings - dataTables settings object
		 *           object:json - json data return from the server.
		 *             The following must be defined:
		 *               iTotalRecords, iTotalDisplayRecords, aaData
		 *             The following may be defined:
		 *               sColumns
		 */
		function _fnAjaxUpdateDraw ( oSettings, json )
		{
			_fnClearTable( oSettings );
			oSettings._iRecordsTotal = json.iTotalRecords;
			oSettings._iRecordsDisplay = json.iTotalDisplayRecords;
			
			/* Determine if reordering is required */
			var sOrdering = _fnColumnOrdering(oSettings);
			bReOrder = (json.sColumns != 'undefined' && sOrdering !== "" && json.sColumns != sOrdering );
			if ( bReOrder )
			{
				var aiIndex = _fnReOrderIndex( oSettings, json.sColumns );
			}
			
			for ( var i=0, iLen=json.aaData.length ; i<iLen ; i++ )
			{
				if ( bReOrder )
				{
					/* If we need to re-order, then create a new array with the correct order and add it */
					var aData = [];
					for ( var j=0, jLen=oSettings.aoColumns.length ; j<jLen ; j++ )
					{
						aData.push( json.aaData[i][ aiIndex[j] ] );
					}
					_fnAddData( oSettings, aData );
				}
				else
				{
					/* No re-order required, sever got it "right" - just straight add */
					_fnAddData( oSettings, json.aaData[i] );
				}
			}
			oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
			
			oSettings.bAjaxDataGet = false;
			_fnDraw( oSettings );
			oSettings.bAjaxDataGet = true;
			_fnProcessingDisplay( oSettings, false );
		}
		
		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
		 * Options (features) HTML
		 */
		
		/*
		 * Function: _fnAddOptionsHtml
		 * Purpose:  Add the options to the page HTML for the table
		 * Returns:  -
		 * Inputs:   object:oSettings - dataTables settings object
		 */
		function _fnAddOptionsHtml ( oSettings )
		{
			/*
			 * Create a temporary, empty, div which we can later on replace with what we have generated
			 * we do it this way to rendering the 'options' html offline - speed :-)
			 */
			var nHolding = document.createElement( 'div' );
			oSettings.nTable.parentNode.insertBefore( nHolding, oSettings.nTable );
			
			/* 
			 * All DataTables are wrapped in a div - this is not currently optional - backwards 
			 * compatability. It can be removed if you don't want it.
			 */
			var nWrapper = document.createElement( 'div' );
			nWrapper.className = "dataTables_wrapper";
			if ( oSettings.sTableId !== '' )
			{
				nWrapper.setAttribute( 'id', oSettings.sTableId+'_wrapper' );
			}
			
			/* Track where we want to insert the option */
			var nInsertNode = nWrapper;
			
			/* IE don't treat strings as arrays */
			var sDom = oSettings.sDomPositioning.split('');
			
			/* Loop over the user set positioning and place the elements as needed */
			for ( var i=0 ; i<sDom.length ; i++ )
			{
				var cOption = sDom[i];
				
				if ( cOption == '<' )
				{
					/* New container div */
					var nNewNode = document.createElement( 'div' );
					
					/* Check to see if we should append a class name to the container */
					var cNext = sDom[i+1];
					if ( cNext == "'" || cNext == '"' )
					{
						var sClass = "";
						var j = 2;
						while ( sDom[i+j] != cNext )
						{
							sClass += sDom[i+j];
							j++;
						}
						nNewNode.className = sClass;
						i += j; /* Move along the position array */
					}
					
					nInsertNode.appendChild( nNewNode );
					nInsertNode = nNewNode;
				}
				else if ( cOption == '>' )
				{
					/* End container div */
					nInsertNode = nInsertNode.parentNode;
				}
				else if ( cOption == 'l' && oSettings.oFeatures.bPaginate && oSettings.oFeatures.bLengthChange )
				{
					/* Length */
					nInsertNode.appendChild( _fnFeatureHtmlLength( oSettings ) );
				}
				else if ( cOption == 'f' && oSettings.oFeatures.bFilter )
				{
					/* Filter */
					nInsertNode.appendChild( _fnFeatureHtmlFilter( oSettings ) );
				}
				else if ( cOption == 'r' && oSettings.oFeatures.bProcessing )
				{
					/* pRocessing */
					nInsertNode.appendChild( _fnFeatureHtmlProcessing( oSettings ) );
				}
				else if ( cOption == 't' )
				{
					/* Table */
					nInsertNode.appendChild( oSettings.nTable );
				}
				else if ( cOption ==  'i' && oSettings.oFeatures.bInfo )
				{
					/* Info */
					nInsertNode.appendChild( _fnFeatureHtmlInfo( oSettings ) );
				}
				else if ( cOption == 'p' && oSettings.oFeatures.bPaginate )
				{
					/* Pagination */
					nInsertNode.appendChild( _fnFeatureHtmlPaginate( oSettings ) );
				}
			}
			
			/* Built our DOM structure - replace the holding div with what we want */
			nHolding.parentNode.replaceChild( nWrapper, nHolding );
		}
		
		/*
		 * Function: _fnFeatureHtmlFilter
		 * Purpose:  Generate the node required for filtering text
		 * Returns:  node
		 * Inputs:   object:oSettings - dataTables settings object
		 */
		function _fnFeatureHtmlFilter ( oSettings )
		{
			var nFilter = document.createElement( 'div' );
			if ( oSettings.sTableId !== '' )
			{
				nFilter.setAttribute( 'id', oSettings.sTableId+'_filter' );
			}
			nFilter.className = "dataTables_filter";
			nFilter.innerHTML = oSettings.oLanguage.sSearch+' <input type="text" value="'+
				oSettings.oPreviousSearch.sSearch.replace('"','&quot;')+'" />';
			
			$("input", nFilter).keyup( function(e) {
				_fnFilterComplete( oSettings, { 
					"sSearch": this.value, 
					"bEscapeRegex": oSettings.oPreviousSearch.bEscapeRegex 
				} );
			} );
			
			return nFilter;
		}
		
		/*
		 * Function: _fnFeatureHtmlInfo
		 * Purpose:  Generate the node required for the info display
		 * Returns:  node
		 * Inputs:   object:oSettings - dataTables settings object
		 */
		function _fnFeatureHtmlInfo ( oSettings )
		{
			var nInfo = document.createElement( 'div' );
			oSettings.nInfo = nInfo;
			
			if ( oSettings.sTableId !== '' )
			{
				oSettings.nInfo.setAttribute( 'id', oSettings.sTableId+'_info' );
			}
			oSettings.nInfo.className = "dataTables_info";
			return nInfo;
		}
		
		/*
		 * Function: _fnFeatureHtmlPaginate
		 * Purpose:  Generate the node required for default pagination
		 * Returns:  node
		 * Inputs:   object:oSettings - dataTables settings object
		 */
		function _fnFeatureHtmlPaginate ( oSettings )
		{
			var nPaginate = document.createElement( 'div' );
			nPaginate.className = "dataTables_paginate";
			
			oSettings.nPaginate = nPaginate;
			$.fn.dataTableExt.oPagination[ oSettings.sPaginationType ].fnInit( oSettings, function( oSettings ) {
				_fnCalculateEnd( oSettings );
				_fnDraw( oSettings );
			} );
			return nPaginate;
		}
		
		/*
		 * Function: _fnFeatureHtmlLength
		 * Purpose:  Generate the node required for user display length changing
		 * Returns:  node
		 * Inputs:   object:oSettings - dataTables settings object
		 */
		function _fnFeatureHtmlLength ( oSettings )
		{
			/* This can be overruled by not using the _MENU_ var/macro in the language variable */
			var sName = (oSettings.sTableId === "") ? "" : 'name="'+oSettings.sTableId+'_length"';
			var sStdMenu = 
				'<select size="1" '+sName+'>'+
					'<option value="10">10</option>'+
					'<option value="25">25</option>'+
					'<option value="50">50</option>'+
					'<option value="100">100</option>'+
				'</select>';
			
			var nLength = document.createElement( 'div' );
			if ( oSettings.sTableId !== '' )
			{
				nLength.setAttribute( 'id', oSettings.sTableId+'_length' );
			}
			nLength.className = "dataTables_length";
			nLength.innerHTML = oSettings.oLanguage.sLengthMenu.replace( '_MENU_', sStdMenu );
			
			/*
			 * Set the length to the current display length - thanks to Andrea Pavlovic for this fix,
			 * and Stefan Skopnik for fixing the fix!
			 */
			$('select option[value="'+oSettings._iDisplayLength+'"]',nLength).attr("selected",true);
			
			$('select', nLength).change( function(e) {
				oSettings._iDisplayLength = parseInt($(this).val(), 10);
				
				_fnCalculateEnd( oSettings );
				
				/* If we have space to show extra rows (backing up from the end point - then do so */
				if ( oSettings._iDisplayEnd == oSettings.aiDisplay.length )
				{
					oSettings._iDisplayStart = oSettings._iDisplayEnd - oSettings._iDisplayLength;
					if ( oSettings._iDisplayStart < 0 )
					{
						oSettings._iDisplayStart = 0;
					}
				}
				
				if ( oSettings._iDisplayLength == -1 )
				{
					oSettings._iDisplayStart = 0;
				}
				
				_fnDraw( oSettings );
			} );
			
			return nLength;
		}
		
		/*
		 * Function: _fnFeatureHtmlProcessing
		 * Purpose:  Generate the node required for the processing node
		 * Returns:  node
		 * Inputs:   object:oSettings - dataTables settings object
		 */
		function _fnFeatureHtmlProcessing ( oSettings )
		{
			var nProcessing = document.createElement( 'div' );
			oSettings.nProcessing = nProcessing;
			
			if ( oSettings.sTableId !== '' )
			{
				oSettings.nProcessing.setAttribute( 'id', oSettings.sTableId+'_processing' );
			}
			oSettings.nProcessing.appendChild( document.createTextNode( oSettings.oLanguage.sProcessing ) );
			oSettings.nProcessing.className = "dataTables_processing";
			oSettings.nProcessing.style.visibility = "hidden";
			oSettings.nTable.parentNode.insertBefore( oSettings.nProcessing, oSettings.nTable );
			
			return nProcessing;
		}
		
		/*
		 * Function: _fnProcessingDisplay
		 * Purpose:  Display or hide the processing indicator
		 * Returns:  -
		 * Inputs:   object:oSettings - dataTables settings object
		 *           bool:
		 *   true - show the processing indicator
		 *   false - don't show
		 */
		function _fnProcessingDisplay ( oSettings, bShow )
		{
			if ( oSettings.oFeatures.bProcessing )
			{
				oSettings.nProcessing.style.visibility = bShow ? "visible" : "hidden";
			}
		}
		
		
		
		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
		 * Filtering
		 */
		
		/*
		 * Function: _fnFilterComplete
		 * Purpose:  Filter the table using both the global filter and column based filtering
		 * Returns:  -
		 * Inputs:   object:oSettings - dataTables settings object
		 *           object:oSearch: search information
		 *           int:iForce - optional - force a research of the master array (1) or not (undefined or 0)
		 */
		function _fnFilterComplete ( oSettings, oInput, iForce )
		{
			/* Filter on everything */
			_fnFilter( oSettings, oInput.sSearch, iForce, oInput.bEscapeRegex );
			
			/* Now do the individual column filter */
			for ( var i=0 ; i<oSettings.aoPreSearchCols.length ; i++ )
			{
				_fnFilterColumn( oSettings, oSettings.aoPreSearchCols[i].sSearch, i, 
					oSettings.aoPreSearchCols[i].bEscapeRegex );
			}
			
			/* Custom filtering */
			if ( $.fn.dataTableExt.afnFiltering.length != 0 )
			{
				_fnFilterCustom( oSettings );
			}
			
			/* Redraw the table */
			if ( typeof oSettings.iInitDisplayStart != 'undefined' && oSettings.iInitDisplayStart != -1 )
			{
				oSettings._iDisplayStart = oSettings.iInitDisplayStart;
				oSettings.iInitDisplayStart = -1;
			}
			else
			{
				oSettings._iDisplayStart = 0;
			}
			_fnCalculateEnd( oSettings );
			_fnDraw( oSettings );
			
			/* Rebuild search array 'offline' */
			_fnBuildSearchArray( oSettings, 0 );
		}
		
		/*
		 * Function: _fnFilterCustom
		 * Purpose:  Apply custom filtering functions
		 * Returns:  -
		 * Inputs:   object:oSettings - dataTables settings object
		 */
		function _fnFilterCustom( oSettings )
		{
			var i, j, iLen, jLen;
			var afnFilters = $.fn.dataTableExt.afnFiltering;
			for ( var i=0, iLen=afnFilters.length ; i<iLen ; i++ )
			{
				var iCorrector = 0;
				for ( var j=0, jLen=oSettings.aiDisplay.length ; j<jLen ; j++ )
				{
					var aData = [];
					var iDisIndex = oSettings.aiDisplay[j-iCorrector];
					
					/* Check if we should use this row based on the filtering function */
					if ( !afnFilters[i]( oSettings, oSettings.aoData[iDisIndex]._aData, iDisIndex ) )
					{
						oSettings.aiDisplay.splice( j-iCorrector, 1 );
						iCorrector++;
					}
				}
			}
		}
		
		/*
		 * Function: _fnFilterColumn
		 * Purpose:  Filter the table on a per-column basis
		 * Returns:  -
		 * Inputs:   object:oSettings - dataTables settings object
		 *           string:sInput - string to filter on
		 *           int:iColumn - column to filter
		 *           bool:bEscapeRegex - escape regex or not
		 */
		function _fnFilterColumn ( oSettings, sInput, iColumn, bEscapeRegex )
		{
			if ( sInput === "" )
			{
				return;
			}
			
			var iIndexCorrector = 0;
			var sRegexMatch = bEscapeRegex ? _fnEscapeRegex( sInput ) : sInput;
			var rpSearch = new RegExp( sRegexMatch, "i" );
			
			for ( i=oSettings.aiDisplay.length-1 ; i>=0 ; i-- )
			{
				var sData = _fnDataToSearch( oSettings.aoData[ oSettings.aiDisplay[i] ]._aData[iColumn],
					oSettings.aoColumns[iColumn].sType );
				if ( ! rpSearch.test( sData ) )
				{
					oSettings.aiDisplay.splice( i, 1 );
					iIndexCorrector++;
				}
			}
		}
		
		/*
		 * Function: _fnFilter
		 * Purpose:  Filter the data table based on user input and draw the table
		 * Returns:  -
		 * Inputs:   object:oSettings - dataTables settings object
		 *           string:sInput - string to filter on
		 *           int:iForce - optional - force a research of the master array (1) or not (undefined or 0)
		 *           bool:bEscapeRegex - escape regex or not
		 */
		function _fnFilter( oSettings, sInput, iForce, bEscapeRegex )
		{
			var flag, i, j;
			
			/* Check if we are forcing or not - optional parameter */
			if ( typeof iForce == 'undefined' || iForce === null )
			{
				iForce = 0;
			}
			
			/* Need to take account of custom filtering functions always */
			if ( $.fn.dataTableExt.afnFiltering.length != 0 )
			{
				iForce = 1;
			}
			
			/* Check if we are re-drawing or not - optional parameter */
			if ( typeof bRedraw == 'undefined' || bRedraw === null )
			{
				bRedraw = true;
			}
			
			/* Generate the regular expression to use. Something along the lines of:
			 * ^(?=.*?\bone\b)(?=.*?\btwo\b)(?=.*?\bthree\b).*$
			 */
			var asSearch = bEscapeRegex ?
				_fnEscapeRegex( sInput ).split( ' ' ) :
				sInput.split( ' ' );
			var sRegExpString = '^(?=.*?'+asSearch.join( ')(?=.*?' )+').*$';
			var rpSearch = new RegExp( sRegExpString, "i" ); /* case insensitive */
			
			/*
			 * If the input is blank - we want the full data set
			 */
			if ( sInput.length <= 0 )
			{
				oSettings.aiDisplay.splice( 0, oSettings.aiDisplay.length);
				oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
			}
			else
			{
				/*
				 * We are starting a new search or the new search string is smaller 
				 * then the old one (i.e. delete). Search from the master array
			 	 */
				if ( oSettings.aiDisplay.length == oSettings.aiDisplayMaster.length ||
					   oSettings.oPreviousSearch.sSearch.length > sInput.length || iForce == 1 ||
					   sInput.indexOf(oSettings.oPreviousSearch.sSearch) !== 0 )
				{
					/* Nuke the old display array - we are going to rebuild it */
					oSettings.aiDisplay.splice( 0, oSettings.aiDisplay.length);
					
					/* Force a rebuild of the search array */
					_fnBuildSearchArray( oSettings, 1 );
					
					/* Search through all records to populate the search array
					 * The the oSettings.aiDisplayMaster and asDataSearch arrays have 1 to 1 
					 * mapping
					 */
					for ( i=0 ; i<oSettings.aiDisplayMaster.length ; i++ )
					{
						if ( rpSearch.test(oSettings.asDataSearch[i]) )
						{
							oSettings.aiDisplay.push( oSettings.aiDisplayMaster[i] );
						}
					}
			  }
			  else
				{
			  	/* Using old search array - refine it - do it this way for speed
			  	 * Don't have to search the whole master array again
			 		 */
			  	var iIndexCorrector = 0;
			  	
			  	/* Search the current results */
			  	for ( i=0 ; i<oSettings.asDataSearch.length ; i++ )
					{
			  		if ( ! rpSearch.test(oSettings.asDataSearch[i]) )
						{
			  			oSettings.aiDisplay.splice( i-iIndexCorrector, 1 );
			  			iIndexCorrector++;
			  		}
			  	}
			  }
			}
			oSettings.oPreviousSearch.sSearch = sInput;
			oSettings.oPreviousSearch.bEscapeRegex = bEscapeRegex;
		}
		
		
		
		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
		 * Sorting
		 */
		
		/*
	 	 * Function: _fnSort
		 * Purpose:  Change the order of the table
		 * Returns:  -
		 * Inputs:   object:oSettings - dataTables settings object
		 *           bool:bApplyClasses - optional - should we apply classes or not
		 * Notes:    We always sort the master array and then apply a filter again
		 *   if it is needed. This probably isn't optimal - but atm I can't think
		 *   of any other way which is (each has disadvantages)
		 */
		function _fnSort ( oSettings, bApplyClasses )
		{
			/*
			 * Funny one this - we want to sort aiDisplayMaster - but according to aoData[]._aData
			 *
			 * function _fnSortText ( a, b )
			 * {
			 * 	var iTest;
			 * 	var oSort = $.fn.dataTableExt.oSort;
			 * 	
			 * 	iTest = oSort['string-asc']( aoData[ a ]._aData[ COL ], aoData[ b ]._aData[ COL ] );
			 * 	if ( iTest === 0 )
			 * 		...
			 * }
			 */
			
			/* Here is what we are looking to achieve here (custom sort functions add complication...)
			 * function _fnSortText ( a, b )
			 * {
			 * 	var iTest;
			 *  var oSort = $.fn.dataTableExt.oSort;
			 * 	iTest = oSort['string-asc']( a[0], b[0] );
			 * 	if ( iTest === 0 )
			 * 		iTest = oSort['string-asc']( a[1], b[1] );
			 * 		if ( iTest === 0 )
			 * 			iTest = oSort['string-asc']( a[2], b[2] );
			 * 	
			 * 	return iTest;
			 * }
			 */
			var aaSort = [];
			var oSort = $.fn.dataTableExt.oSort;
			var aoData = oSettings.aoData;
			var iDataSort;
			var iDataType;
			var i;
			
			if ( oSettings.aaSorting.length !== 0 || oSettings.aaSortingFixed !== null )
			{
				if ( oSettings.aaSortingFixed !== null )
				{
					aaSort = oSettings.aaSortingFixed.concat( oSettings.aaSorting );
				}
				else
				{
					aaSort = oSettings.aaSorting.slice();
				}
				
				if ( !window.runtime )
				{
					var sDynamicSort = "var fnLocalSorting = function(a,b){"+
						"var iTest;";
					
					for ( i=0 ; i<aaSort.length-1 ; i++ )
					{
						iDataSort = oSettings.aoColumns[ aaSort[i][0] ].iDataSort;
						iDataType = oSettings.aoColumns[ iDataSort ].sType;
						sDynamicSort += "iTest = oSort['"+iDataType+"-"+aaSort[i][1]+"']"+
							"( aoData[a]._aData["+iDataSort+"], aoData[b]._aData["+iDataSort+"] ); if ( iTest === 0 )";
					}
					
					iDataSort = oSettings.aoColumns[ aaSort[aaSort.length-1][0] ].iDataSort;
					iDataType = oSettings.aoColumns[ iDataSort ].sType;
					sDynamicSort += "iTest = oSort['"+iDataType+"-"+aaSort[aaSort.length-1][1]+"']"+
						"( aoData[a]._aData["+iDataSort+"], aoData[b]._aData["+iDataSort+"] ); return iTest;}";
					
					/* The eval has to be done to a variable for IE */
					eval( sDynamicSort );
					oSettings.aiDisplayMaster.sort( fnLocalSorting );
				}
				else
				{
					/*
					 * Support for Adobe AIR - AIR doesn't allow eval with a function
					 * Note that for reasonable sized data sets this method is around 1.5 times slower than
					 * the eval above (hence why it is not used all the time). Oddly enough, it is ever so
					 * slightly faster for very small sets (presumably the eval has overhead).
					 *   Single column (1083 records) - eval: 32mS   AIR: 38mS
					 *   Two columns (1083 records) -   eval: 55mS   AIR: 66mS
					 */
					
					/* Build a cached array so the sort doesn't have to process this stuff on every call */
					var aAirSort = [];
					var iLen = aaSort.length;
					for ( i=0 ; i<iLen ; i++ )
					{
						iDataSort = oSettings.aoColumns[ aaSort[i][0] ].iDataSort;
						aAirSort.push( [
							iDataSort,
							oSettings.aoColumns[ iDataSort ].sType+'-'+aaSort[i][1]
						] );
					}
					
					oSettings.aiDisplayMaster.sort( function (a,b) {
						var iTest;
						for ( var i=0 ; i<iLen ; i++ )
						{
							iTest = oSort[ aAirSort[i][1] ]( aoData[a]._aData[aAirSort[i][0]], aoData[b]._aData[aAirSort[i][0]] );
							if ( iTest !== 0 )
							{
								return iTest;
							}
						}
						return 0;
					} );
				}
			}
			
			/* Alter the sorting classes to take account of the changes */
			if ( typeof bApplyClasses == 'undefined' || bApplyClasses )
			{
				_fnSortingClasses( oSettings );
			}
			
			/* Copy the master data into the draw array and re-draw */
			if ( oSettings.oFeatures.bFilter )
			{
				/* _fnFilter() will redraw the table for us */
				_fnFilterComplete( oSettings, oSettings.oPreviousSearch, 1 );
			}
			else
			{
				oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
				oSettings._iDisplayStart = 0; /* reset display back to page 0 */
				_fnCalculateEnd( oSettings );
				_fnDraw( oSettings );
			}
		}
		
		/*
		 * Function: _fnSortingClasses
		 * Purpose:  Set the sortting classes on the header
		 * Returns:  -
		 * Inputs:   object:oSettings - dataTables settings object
		 */
		function _fnSortingClasses( oSettings )
		{
			var i;
			var aaSort;
			var iColumns = oSettings.aoColumns.length;
			for ( i=0 ; i<iColumns ; i++ )
			{
				$(oSettings.aoColumns[i].nTh).removeClass( "sorting_asc sorting_desc sorting" );
			}
			
			if ( oSettings.aaSortingFixed !== null )
			{
				aaSort = oSettings.aaSortingFixed.concat( oSettings.aaSorting );
			}
			else
			{
				aaSort = oSettings.aaSorting.slice();
			}
			
			/* Apply the required classes to the header */
			for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
			{
				if ( oSettings.aoColumns[i].bSortable && oSettings.aoColumns[i].bVisible )
				{
					var sClass = "sorting";
					for ( var j=0 ; j<aaSort.length ; j++ )
					{
						if ( aaSort[j][0] == i )
						{
							sClass = ( aaSort[j][1] == "asc" ) ?
								"sorting_asc" : "sorting_desc";
							break;
						}
					}
					$(oSettings.aoColumns[i].nTh).addClass( sClass );
				}
			}
			
			/* 
			 * Apply the required classes to the table body
			 * Note that this is given as a feature switch since it can significantly slow down a sort
			 * on large data sets (adding and removing of classes is always slow at the best of times..)
			 */
			if ( oSettings.oFeatures.bSortClasses )
			{
				var nTrs = _fnGetTrNodes( oSettings );
				$('td', nTrs).removeClass( 'sorting_1 sorting_2 sorting_3' );
				
				var iClass = 1;
				for ( i=0 ; i<aaSort.length ; i++ )
				{
					var iVis = _fnColumnIndexToVisible(oSettings, aaSort[i][0]);
					if ( iVis !== null )
					{
						/* Limit the number of classes to three */
						if ( iClass <= 2 )
						{
							$('td:eq('+iVis+')', nTrs).addClass( 'sorting_'+iClass );
						}
						else
						{
							$('td:eq('+iVis+')', nTrs).addClass( 'sorting_3' );
						}
						iClass++;
					}
				}
			}
		}
		
		/*
		 * Function: _fnVisibleToColumnIndex
		 * Purpose:  Covert the index of a visible column to the index in the data array (take account
		 *   of hidden columns)
		 * Returns:  int:i - the data index
		 * Inputs:   object:oSettings - dataTables settings object
		 */
		function _fnVisibleToColumnIndex( oSettings, iMatch )
		{
			var iColumn = -1;
			
			for ( var i=0 ; i<oSettings.aoColumns.length ; i++ )
			{
				if ( oSettings.aoColumns[i].bVisible === true )
				{
					iColumn++;
				}
				
				if ( iColumn == iMatch )
				{
					return i;
				}
			}
			
			return null;
		}
		
		/*
		 * Function: _fnColumnIndexToVisible
		 * Purpose:  Covert the index of an index in the data array and convert it to the visible
		 *   column index (take account of hidden columns)
		 * Returns:  int:i - the data index
		 * Inputs:   object:oSettings - dataTables settings object
		 */
		function _fnColumnIndexToVisible( oSettings, iMatch )
		{
			var iVisible = -1;
			for ( var i=0 ; i<oSettings.aoColumns.length ; i++ )
			{
				if ( oSettings.aoColumns[i].bVisible === true )
				{
					iVisible++;
				}
				
				if ( i == iMatch )
				{
					return oSettings.aoColumns[i].bVisible === true ? iVisible : null;
				}
			}
			
			return null;
		}
		
		/*
		 * Function: _fnVisbleColumns
		 * Purpose:  Get the number of visible columns
		 * Returns:  int:i - the number of visible columns
		 * Inputs:   object:oS - dataTables settings object
		 */
		function _fnVisbleColumns( oS )
		{
			var iVis = 0;
			for ( var i=0 ; i<oS.aoColumns.length ; i++ )
			{
				if ( oS.aoColumns[i].bVisible === true )
				{
					iVis++;
				}
			}
			return iVis;
		}
		
		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
		 * Support functions
		 */
		
		/*
		 * Function: _fnBuildSearchArray
		 * Purpose:  Create an array which can be quickly search through
		 * Returns:  -
		 * Inputs:   object:oSettings - dataTables settings object
		 *           int:iMaster - use the master data array - optional
		 */
		function _fnBuildSearchArray ( oSettings, iMaster )
		{
			/* Clear out the old data */
			oSettings.asDataSearch.splice( 0, oSettings.asDataSearch.length );
			
			var aArray = (typeof iMaster != 'undefined' && iMaster == 1) ?
			 	oSettings.aiDisplayMaster : oSettings.aiDisplay;
			
			for ( i=0 ; i<aArray.length ; i++ )
			{
				oSettings.asDataSearch[i] = '';
				for ( j=0 ; j<oSettings.aoColumns.length ; j++ )
				{
					if ( oSettings.aoColumns[j].bSearchable )
					{
						var sData = oSettings.aoData[ aArray[i] ]._aData[j];
						oSettings.asDataSearch[i] += _fnDataToSearch( sData, oSettings.aoColumns[j].sType )+' ';
					}
				}
			}
		}
		
		/*
		 * Function: _fnDataToSearch
		 * Purpose:  Convert raw data into something that the user can search on
		 * Returns:  string: - search string
		 * Inputs:   string:sData - data to be modified
		 *           string:sType - data type
		 */
		function _fnDataToSearch ( sData, sType )
		{
			if ( sType == "html" )
			{
				return sData.replace(/\n/g," ").replace( /<.*?>/g, "" );
			}
			else if ( typeof sData == "string" )
			{
				return sData.replace(/\n/g," ");
			}
			return sData;
		}
		
		/*
		 * Function: _fnCalculateEnd
		 * Purpose:  Rcalculate the end point based on the start point
		 * Returns:  -
		 * Inputs:   object:oSettings - dataTables settings object
		 */
		function _fnCalculateEnd( oSettings )
		{
			if ( oSettings.oFeatures.bPaginate === false )
			{
				oSettings._iDisplayEnd = oSettings.aiDisplay.length;
			}
			else
			{
				/* Set the end point of the display - based on how many elements there are
				 * still to display
				 */
				if ( oSettings._iDisplayStart + oSettings._iDisplayLength > oSettings.aiDisplay.length ||
					   oSettings._iDisplayLength == -1 )
				{
					oSettings._iDisplayEnd = oSettings.aiDisplay.length;
				}
				else
				{
					oSettings._iDisplayEnd = oSettings._iDisplayStart + oSettings._iDisplayLength;
				}
			}
		}
		
		/*
		 * Function: _fnConvertToWidth
		 * Purpose:  Convert a CSS unit width to pixels (e.g. 2em)
		 * Returns:  int:iWidth - width in pixels
		 * Inputs:   string:sWidth - width to be converted
		 *           node:nParent - parent to get the with for (required for
		 *             relative widths) - optional
		 */
		function _fnConvertToWidth ( sWidth, nParent )
		{
			if ( !sWidth || sWidth === null || sWidth === '' )
			{
				return 0;
			}
			
			if ( typeof nParent == "undefined" )
			{
				nParent = document.getElementsByTagName('body')[0];
			}
			
			var iWidth;
			var nTmp = document.createElement( "div" );
			nTmp.style.width = sWidth;
			
			nParent.appendChild( nTmp );
			iWidth = nTmp.offsetWidth;
			nParent.removeChild( nTmp );
			
			return ( iWidth );
		}
		
		/*
		 * Function: _fnCalculateColumnWidths
		 * Purpose:  Calculate the width of columns for the table
		 * Returns:  -
		 * Inputs:   object:oSettings - dataTables settings object
		 */
		function _fnCalculateColumnWidths ( oSettings )
		{
			var iTableWidth = oSettings.nTable.offsetWidth;
			var iTotalUserIpSize = 0;
			var iTmpWidth;
			var iVisibleColumns = 0;
			var iColums = oSettings.aoColumns.length;
			var i;
			var oHeaders = $('thead th', oSettings.nTable);
			
			/* Convert any user input sizes into pixel sizes */
			for ( i=0 ; i<iColums ; i++ )
			{
				if ( oSettings.aoColumns[i].bVisible )
				{
					iVisibleColumns++;
					
					if ( oSettings.aoColumns[i].sWidth !== null )
					{
						iTmpWidth = _fnConvertToWidth( oSettings.aoColumns[i].sWidth, 
							oSettings.nTable.parentNode );
						
						/* Total up the user defined widths for later calculations */
						iTotalUserIpSize += iTmpWidth;
						
						oSettings.aoColumns[i].sWidth = iTmpWidth+"px";
					}
				}
			}
			
			/* If the number of columns in the DOM equals the number that we
			 * have to process in dataTables, then we can use the offsets that are
			 * created by the web-browser. No custom sizes can be set in order for
			 * this to happen
			 */
			if ( iColums == oHeaders.length && iTotalUserIpSize === 0 && iVisibleColumns == iColums )
			{
				for ( i=0 ; i<oSettings.aoColumns.length ; i++ )
				{
					oSettings.aoColumns[i].sWidth = oHeaders[i].offsetWidth+"px";
				}
			}
			else
			{
				/* Otherwise we are going to have to do some calculations to get
				 * the width of each column. Construct a 1 row table with the maximum
				 * string sizes in the data, and any user defined widths
				 */
				var nCalcTmp = oSettings.nTable.cloneNode( false );
				nCalcTmp.setAttribute( "id", '' );
				
				var sTableTmp = '<table class="'+nCalcTmp.className+'">';
				var sCalcHead = "<tr>";
				var sCalcHtml = "<tr>";
				
				/* Construct a tempory table which we will inject (invisibly) into
				 * the dom - to let the browser do all the hard word
				 */
				for ( i=0 ; i<iColums ; i++ )
				{
					if ( oSettings.aoColumns[i].bVisible )
					{
						sCalcHead += '<th>'+oSettings.aoColumns[i].sTitle+'</th>';
						
						if ( oSettings.aoColumns[i].sWidth !== null )
						{
							var sWidth = '';
							if ( oSettings.aoColumns[i].sWidth !== null )
							{
								sWidth = ' style="width:'+oSettings.aoColumns[i].sWidth+';"';
							}
							
							sCalcHtml += '<td'+sWidth+' tag_index="'+i+'">'+fnGetMaxLenString( oSettings, i)+'</td>';
						}
						else
						{
							sCalcHtml += '<td tag_index="'+i+'">'+fnGetMaxLenString( oSettings, i)+'</td>';
						}
					}
				}
				
				sCalcHead += "</tr>";
				sCalcHtml += "</tr>";
				
				/* Create the tmp table node (thank you jQuery) */
				nCalcTmp = $( sTableTmp + sCalcHead + sCalcHtml +'</table>' )[0];
				nCalcTmp.style.width = iTableWidth + "px";
				nCalcTmp.style.visibility = "hidden";
				nCalcTmp.style.position = "absolute"; /* Try to aviod scroll bar */
				
				oSettings.nTable.parentNode.appendChild( nCalcTmp );
				
				var oNodes = $("td", nCalcTmp);
				var iIndex;
				
				/* Gather in the browser calculated widths for the rows */
				for ( i=0 ; i<oNodes.length ; i++ )
				{
					iIndex = oNodes[i].getAttribute('tag_index');
					
					oSettings.aoColumns[iIndex].sWidth = $("td", nCalcTmp)[i].offsetWidth +"px";
				}
				
				oSettings.nTable.parentNode.removeChild( nCalcTmp );
			}
		}
		
		/*
		 * Function: fnGetMaxLenString
		 * Purpose:  Get the maximum strlen for each data column
		 * Returns:  string: - max strlens for each column
		 * Inputs:   object:oSettings - dataTables settings object
		 *           int:iCol - column of interest
		 */
		function fnGetMaxLenString( oSettings, iCol )
		{
			var iMax = 0;
			var iMaxIndex = -1;
			
			for ( var i=0 ; i<oSettings.aoData.length ; i++ )
			{
				if ( oSettings.aoData[i]._aData[iCol].length > iMax )
				{
					iMax = oSettings.aoData[i]._aData[iCol].length;
					iMaxIndex = i;
				}
			}
			
			if ( iMaxIndex >= 0 )
			{
				return oSettings.aoData[iMaxIndex]._aData[iCol];
			}
			return '';
		}
		
		/*
		 * Function: _fnArrayCmp
		 * Purpose:  Compare two arrays
		 * Returns:  0 if match, 1 if length is different, 2 if no match
		 * Inputs:   array:aArray1 - first array
		 *           array:aArray2 - second array
		 */
		function _fnArrayCmp( aArray1, aArray2 )
		{
			if ( aArray1.length != aArray2.length )
			{
				return 1;
			}
			
			for ( var i=0 ; i<aArray1.length ; i++ )
			{
				if ( aArray1[i] != aArray2[i] )
				{
					return 2;
				}
			}
			
			return 0;
		}
		
		/*
		 * Function: _fnDetectType
		 * Purpose:  Get the sort type based on an input string
		 * Returns:  string: - type (defaults to 'string' if no type can be detected)
		 * Inputs:   string:sData - data we wish to know the type of
		 * Notes:    This function makes use of the DataTables plugin objct $.fn.dataTableExt 
		 *   (.aTypes) such that new types can easily be added.
		 */
		function _fnDetectType( sData )
		{
			var aTypes = $.fn.dataTableExt.aTypes;
			var iLen = aTypes.length;
			
			for ( var i=0 ; i<iLen ; i++ )
			{
				var sType = aTypes[i]( sData );
				if ( sType !== null )
				{
					return sType;
				}
			}
			
			return 'string';
		}
		
		/*
		 * Function: _fnSettingsFromNode
		 * Purpose:  Return the settings object for a particular table
		 * Returns:  object: Settings object - or null if not found
		 * Inputs:   node:nTable - table we are using as a dataTable
		 */
		function _fnSettingsFromNode ( nTable )
		{
			for ( var i=0 ; i<_aoSettings.length ; i++ )
			{
				if ( _aoSettings[i].nTable == nTable )
				{
					return _aoSettings[i];
				}
			}
			
			return null;
		}
		
		/*
		 * Function: _fnGetDataMaster
		 * Purpose:  Return an array with the full table data
		 * Returns:  array array:aData - Master data array
		 * Inputs:   object:oSettings - dataTables settings object
		 */
		function _fnGetDataMaster ( oSettings )
		{
			var aData = [];
			var iLen = oSettings.aoData.length;
			for ( var i=0 ; i<iLen; i++ )
			{
				aData.push( oSettings.aoData[i]._aData );
			}
			return aData;
		}
		
		/*
		 * Function: _fnGetTrNodes
		 * Purpose:  Return an array with the TR nodes for the table
		 * Returns:  array array:aData - TR array
		 * Inputs:   object:oSettings - dataTables settings object
		 */
		function _fnGetTrNodes ( oSettings )
		{
			var aNodes = [];
			var iLen = oSettings.aoData.length;
			for ( var i=0 ; i<iLen ; i++ )
			{
				aNodes.push( oSettings.aoData[i].nTr );
			}
			return aNodes;
		}
		
		/*
		 * Function: _fnEscapeRegex
		 * Purpose:  scape a string stuch that it can be used in a regular expression
		 * Returns:  string: - escaped string
		 * Inputs:   string:sVal - string to escape
		 */
		function _fnEscapeRegex ( sVal )
		{
			var acEscape = [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '$', '^' ];
		  var reReplace = new RegExp( '(\\' + acEscape.join('|\\') + ')', 'g' );
		  return sVal.replace(reReplace, '\\$1');
		}
		
		/*
		 * Function: _fnReOrderIndex
		 * Purpose:  Figure out how to reorder a display list
		 * Returns:  array int:aiReturn - index list for reordering
		 * Inputs:   object:oSettings - dataTables settings object
		 */
		function _fnReOrderIndex ( oSettings, sColumns )
		{
			var aColumns = sColumns.split(',');
			var aiReturn = [];
			
			for ( var i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
			{
				for ( var j=0 ; j<iLen ; j++ )
				{
					if ( oSettings.aoColumns[i].sName == aColumns[j] )
					{
						aiReturn.push( j );
						break;
					}
				}
			}
			
			return aiReturn;
		}
		
		/*
		 * Function: _fnColumnOrdering
		 * Purpose:  Get the column ordering that DataTables expects
		 * Returns:  string: - comma separated list of names
		 * Inputs:   object:oSettings - dataTables settings object
		 */
		function _fnColumnOrdering ( oSettings )
		{
			var sNames = '';
			for ( var i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
			{
				sNames += oSettings.aoColumns[i].sName+',';
			}
			if ( sNames.length == iLen )
			{
				return "";
			}
			return sNames.slice(0, -1);
		}
		
		/*
		 * Function: _fnClearTable
		 * Purpose:  Nuke the table
		 * Returns:  -
		 * Inputs:   object:oSettings - dataTables settings object
		 */
		function _fnClearTable( oSettings )
		{
			oSettings.aoData.length = 0;
			oSettings.aiDisplayMaster.length = 0;
			oSettings.aiDisplay.length = 0;
			_fnCalculateEnd( oSettings );
		}
		
		/*
		 * Function: _fnSaveState
		 * Purpose:  Save the state of a table in a cookie such that the page can be reloaded
		 * Returns:  -
		 * Inputs:   object:oSettings - dataTables settings object
		 */
		function _fnSaveState ( oSettings )
		{
			if ( !oSettings.oFeatures.bStateSave )
			{
				return;
			}
			
			/* Store the interesting variables */
			var sValue = "{";
			sValue += '"iStart": '+oSettings._iDisplayStart+',';
			sValue += '"iEnd": '+oSettings._iDisplayEnd+',';
			sValue += '"iLength": '+oSettings._iDisplayLength+',';
			sValue += '"sFilter": "'+oSettings.oPreviousSearch.sSearch.replace('"','\\"')+'",';
			sValue += '"sFilterEsc": '+oSettings.oPreviousSearch.bEscapeRegex+',';
			
			sValue += '"aaSorting": [ ';
			for ( var i=0 ; i<oSettings.aaSorting.length ; i++ )
			{
				sValue += "["+oSettings.aaSorting[i][0]+",'"+oSettings.aaSorting[i][1]+"'],";
			}
			sValue = sValue.substring(0, sValue.length-1);
			sValue += "],";
			
			sValue += '"aaSearchCols": [ ';
			for ( var i=0 ; i<oSettings.aoPreSearchCols.length ; i++ )
			{
				sValue += "['"+oSettings.aoPreSearchCols[i].sSearch.replace("'","\'")+
					"',"+oSettings.aoPreSearchCols[i].bEscapeRegex+"],";
			}
			sValue = sValue.substring(0, sValue.length-1);
			sValue += "]";
			sValue += "}";
			
			_fnCreateCookie( "SpryMedia_DataTables_"+oSettings.sInstance, sValue, 
				oSettings.iCookieDuration );
		}
		
		/*
		 * Function: _fnLoadState
		 * Purpose:  Attempt to load a saved table state from a cookie
		 * Returns:  -
		 * Inputs:   object:oSettings - dataTables settings object
		 */
		function _fnLoadState ( oSettings )
		{
			if ( !oSettings.oFeatures.bStateSave )
			{
				return;
			}
			
			var sData = _fnReadCookie( "SpryMedia_DataTables_"+oSettings.sInstance );
			if ( sData !== null && sData !== '' )
			{
				/* Try/catch the JSON eval - if it is bad then we ignore it */
				try
				{
					/* Use the JSON library for safety - if it is available */
					if ( typeof JSON == 'object' && typeof JSON.parse == 'function' )
					{
						/* DT 1.4.0 used single quotes for a string - JSON.parse doesn't allow this and throws
						 * an error. So for now we can do this. This can be removed in future it is just to
						 * allow the tranfrer to 1.4.1+ to occur
						 */
						oData = JSON.parse( sData.replace(/'/g, '"') );
					}
					else
					{
						oData = eval( '('+sData+')' );
					}
				}
				catch( e )
				{
					return;
				}
				
				/* Restore key features */
				oSettings._iDisplayStart = oData.iStart;
				oSettings.iInitDisplayStart = oData.iStart;
				oSettings._iDisplayEnd = oData.iEnd;
				oSettings._iDisplayLength = oData.iLength;
				oSettings.oPreviousSearch.sSearch = oData.sFilter;
				oSettings.aaSorting = oData.aaSorting.slice();
				
				/* Search filtering - global reference added in 1.4.1 */
				if ( typeof oData.sFilterEsc != 'undefined' )
				{
					oSettings.oPreviousSearch.bEscapeRegex = oData.sFilterEsc;
				}
				
				/* Column filtering - added in 1.5.0 beta 6 */
				if ( typeof oData.aaSearchCols != 'undefined' )
				{
					for ( var i=0 ; i<oData.aaSearchCols.length ; i++ )
					{
						oSettings.aoPreSearchCols[i] = {
							"sSearch": oData.aaSearchCols[i][0],
							"bEscapeRegex": oData.aaSearchCols[i][1]
						}
					}
				}
			}
		}
		
		/*
		 * Function: _fnCreateCookie
		 * Purpose:  Create a new cookie with a value to store the state of a table
		 * Returns:  -
		 * Inputs:   string:sName - name of the cookie to create
		 *           string:sValue - the value the cookie should take
		 *           int:iSecs - duration of the cookie
		 */
		function _fnCreateCookie ( sName, sValue, iSecs )
		{
			var date = new Date();
			date.setTime( date.getTime()+(iSecs*1000) );
			
			/* 
			 * Shocking but true - it would appear IE has major issues with having the path being
			 * set to anything but root. We need the cookie to be available based on the path, so we
			 * have to append the pathname to the cookie name. Appalling.
			 */
			sName += '_'+window.location.pathname.replace(/[\/:]/g,"").toLowerCase();
			
			document.cookie = sName+"="+sValue+"; expires="+date.toGMTString()+"; path=/";
		}
		
		/*
		 * Function: _fnReadCookie
		 * Purpose:  Read an old cookie to get a cookie with an old table state
		 * Returns:  string: - contents of the cookie - or null if no cookie with that name found
		 * Inputs:   string:sName - name of the cookie to read
		 */
		function _fnReadCookie ( sName )
		{
			var sNameEQ = sName +'_'+ window.location.pathname.replace(/[\/:]/g,"").toLowerCase() + "=";
			var sCookieContents = document.cookie.split(';');
			
			for( var i=0 ; i<sCookieContents.length ; i++ )
			{
				var c = sCookieContents[i];
				
				while (c.charAt(0)==' ')
				{
					c = c.substring(1,c.length);
				}
				
				if (c.indexOf(sNameEQ) === 0)
				{
					return c.substring(sNameEQ.length,c.length);
				}
			}
			return null;
		}
		
		/*
		 * Function: _fnGetUniqueThs
		 * Purpose:  Get an array of unique th elements, one for each column
		 * Returns:  array node:aReturn - list of unique ths
		 * Inputs:   node:nThead - The thead element for the table
		 */
		function _fnGetUniqueThs ( nThead )
		{
			var nTrs = nThead.getElementsByTagName('tr');
			
			/* Nice simple case */
			if ( nTrs.length == 1 )
			{
				return nTrs[0].getElementsByTagName('th');
			}
			
			/* Otherwise we need to figure out the layout array to get the nodes */
			var aLayout = [], aReturn = [];
			var ROWSPAN = 2, COLSPAN = 3;
			var i, j, k, iLen, jLen, iColumnShifted;
			var fnShiftCol = function ( a, i, j ) {
				while ( typeof a[i][j] != 'undefined' ) {
					j++;
				}
				return j;
			};
			var fnAddRow = function ( i ) {
				if ( typeof aLayout[i] == 'undefined' ) {
					aLayout[i] = [];
				}
			};
			
			/* Calculate a layout array */
			for ( i=0, iLen=nTrs.length ; i<iLen ; i++ )
			{
				fnAddRow( i );
				var iColumn = 0;
				var nTds = nTrs[i].getElementsByTagName('th');
				
				for ( j=0, jLen=nTds.length ; j<jLen ; j++ )
				{
					var iColspan = nTds[j].getAttribute('colspan') * 1;
					var iRowspan = nTds[j].getAttribute('rowspan') * 1;
					
					if ( !iColspan || iColspan==0 || iColspan==1 )
					{
						iColumnShifted = fnShiftCol( aLayout, i, iColumn );
						aLayout[i][iColumnShifted] = nTds[j];
						if ( iRowspan || iRowspan==0 || iRowspan==1 )
						{
							for ( k=1 ; k<iRowspan ; k++ )
							{
								fnAddRow( i+k );
								aLayout[i+k][iColumnShifted] = ROWSPAN;
							}
						}
						iColumn++;
					}
					else
					{
						iColumnShifted = fnShiftCol( aLayout, i, iColumn );
						for ( k=0 ; k<iColspan ; k++ )
						{
							aLayout[i][iColumnShifted+k] = COLSPAN;
						}
						iColumn += iColspan;
					}
				}
			}
			
			/* Convert the layout array into a node array */
			for ( i=0, iLen=aLayout[0].length ; i<iLen ; i++ )
			{
				for ( j=0, jLen=aLayout.length ; j<jLen ; j++ )
				{
					if ( typeof aLayout[j][i] == 'object' )
					{
						aReturn.push( aLayout[j][i] );
					}
				}
			}
			
			return aReturn;
		}
		
		/*
		 * Function: _fnMap
		 * Purpose:  See if a property is defined on one object, if so assign it to the other object
		 * Returns:  - (done by reference)
		 * Inputs:   object:oRet - target object
		 *           object:oSrc - source object
		 *           string:sName - property
		 *           string:sMappedName - name to map too - optional, sName used if not given
		 */
		function _fnMap( oRet, oSrc, sName, sMappedName )
		{
			if ( typeof sMappedName == 'undefined' )
			{
				sMappedName = sName;
			}
			if ( typeof oSrc[sName] != 'undefined' )
			{
				oRet[sMappedName] = oSrc[sName];
			}
		}
		
		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
		 * API
		 * 
		 * I'm not overly happy with this solution - I'd much rather that there was a way of getting
		 * a list of all the private functions and do what we need to dynamically - but that doesn't
		 * appear to be possible. Bonkers. A better solution would be to provide a 'bind' type object.
		 */
		if ( bApi )
		{
			this.oApi._fnInitalise = _fnInitalise;
			this.oApi._fnLanguageProcess = _fnLanguageProcess;
			this.oApi._fnAddColumn = _fnAddColumn;
			this.oApi._fnAddData = _fnAddData;
			this.oApi._fnGatherData = _fnGatherData;
			this.oApi._fnDrawHead = _fnDrawHead;
			this.oApi._fnDraw = _fnDraw;
			this.oApi._fnAjaxUpdate = _fnAjaxUpdate;
			this.oApi._fnAddOptionsHtml = _fnAddOptionsHtml;
			this.oApi._fnFeatureHtmlFilter = _fnFeatureHtmlFilter;
			this.oApi._fnFeatureHtmlInfo = _fnFeatureHtmlInfo;
			this.oApi._fnFeatureHtmlPaginate = _fnFeatureHtmlPaginate;
			this.oApi._fnFeatureHtmlLength = _fnFeatureHtmlLength;
			this.oApi._fnFeatureHtmlProcessing = _fnFeatureHtmlProcessing;
			this.oApi._fnProcessingDisplay = _fnProcessingDisplay;
			this.oApi._fnFilterComplete = _fnFilterComplete;
			this.oApi._fnFilterColumn = _fnFilterColumn;
			this.oApi._fnFilter = _fnFilter;
			this.oApi._fnSortingClasses = _fnSortingClasses;
			this.oApi._fnVisibleToColumnIndex = _fnVisibleToColumnIndex;
			this.oApi._fnColumnIndexToVisible = _fnColumnIndexToVisible;
			this.oApi._fnVisbleColumns = _fnVisbleColumns;
			this.oApi._fnBuildSearchArray = _fnBuildSearchArray;
			this.oApi._fnDataToSearch = _fnDataToSearch;
			this.oApi._fnCalculateEnd = _fnCalculateEnd;
			this.oApi._fnConvertToWidth = _fnConvertToWidth;
			this.oApi._fnCalculateColumnWidths = _fnCalculateColumnWidths;
			this.oApi._fnArrayCmp = _fnArrayCmp;
			this.oApi._fnDetectType = _fnDetectType;
			this.oApi._fnGetDataMaster = _fnGetDataMaster;
			this.oApi._fnGetTrNodes = _fnGetTrNodes;
			this.oApi._fnEscapeRegex = _fnEscapeRegex;
			this.oApi._fnReOrderIndex = _fnReOrderIndex;
			this.oApi._fnColumnOrdering = _fnColumnOrdering;
			this.oApi._fnClearTable = _fnClearTable;
			this.oApi._fnSaveState = _fnSaveState;
			this.oApi._fnLoadState = _fnLoadState;
			this.oApi._fnCreateCookie = _fnCreateCookie;
			this.oApi._fnReadCookie = _fnReadCookie;
			this.oApi._fnGetUniqueThs = _fnGetUniqueThs;
		}
		
		
		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
		 * Constructor
		 */
		return this.each(function()
		{
			/* Make a complete and independent copy of the settings object */
			var oSettings = new classSettings();
			_aoSettings.push( oSettings );
			
			var i=0, iLen;
			var bInitHandedOff = false;
			var bUsePassedData = false;
			
			/* Set the id */
			var sId = this.getAttribute( 'id' );
			if ( sId !== null )
			{
				oSettings.sTableId = sId;
				oSettings.sInstance = sId;
			}
			else
			{
				oSettings.sInstance = $.fn.dataTableExt._oExternConfig.iNextUnique ++;
			}
			
			/* Set the table node */
			oSettings.nTable = this;
			
			/* Store the features that we have available */
			if ( typeof oInit != 'undefined' && oInit !== null )
			{
				_fnMap( oSettings.oFeatures, oInit, "bPaginate" );
				_fnMap( oSettings.oFeatures, oInit, "bLengthChange" );
				_fnMap( oSettings.oFeatures, oInit, "bFilter" );
				_fnMap( oSettings.oFeatures, oInit, "bSort" );
				_fnMap( oSettings.oFeatures, oInit, "bInfo" );
				_fnMap( oSettings.oFeatures, oInit, "bProcessing" );
				_fnMap( oSettings.oFeatures, oInit, "bAutoWidth" );
				_fnMap( oSettings.oFeatures, oInit, "bSortClasses" );
				_fnMap( oSettings.oFeatures, oInit, "bServerSide" );
				_fnMap( oSettings, oInit, "asStripClasses" );
				_fnMap( oSettings, oInit, "fnRowCallback" );
				_fnMap( oSettings, oInit, "fnHeaderCallback" );
				_fnMap( oSettings, oInit, "fnFooterCallback" );
				_fnMap( oSettings, oInit, "fnDrawCallback" );
				_fnMap( oSettings, oInit, "fnInitComplete" );
				_fnMap( oSettings, oInit, "fnServerData" );
				_fnMap( oSettings, oInit, "aaSorting" );
				_fnMap( oSettings, oInit, "aaSortingFixed" );
				_fnMap( oSettings, oInit, "sPaginationType" );
				_fnMap( oSettings, oInit, "sAjaxSource" );
				_fnMap( oSettings, oInit, "sDom", "sDomPositioning" );
				_fnMap( oSettings, oInit, "oSearch", "oPreviousSearch" );
				_fnMap( oSettings, oInit, "aoSearchCols", "aoPreSearchCols" );
				_fnMap( oSettings, oInit, "iDisplayLength", "_iDisplayLength" );
				
				if ( typeof oInit.iDisplayStart != 'undefined' && 
				     typeof oSettings.iInitDisplayStart == 'undefined' ) {
					/* Display start point, taking into account the save saving */
					oSettings.iInitDisplayStart = oInit.iDisplayStart;
					oSettings._iDisplayStart = oInit.iDisplayStart;
				}
				
				
				if ( typeof oInit.aaData != 'undefined' ) {
					bUsePassedData = true;
				}
				
				/* Must be done after everything which can be overridden by a cookie! */
				if ( typeof oInit.bStateSave != 'undefined' )
				{
					oSettings.oFeatures.bStateSave = oInit.bStateSave;
					_fnLoadState( oSettings );
				}
				
				/* Backwards compatability */
				/* aoColumns / aoData - remove at some point... */
				if ( typeof oInit != 'undefined' && typeof oInit.aoData != 'undefined' )
				{
					oInit.aoColumns = oInit.aoData;
				}
				
				/* Language definitions */
				if ( typeof oInit.oLanguage != 'undefined' )
				{
					if ( typeof oInit.oLanguage.sUrl != 'undefined' )
					{
						/* Get the language definitions from a file */
						oSettings.oLanguage.sUrl = oInit.oLanguage.sUrl;
						$.getJSON( oSettings.oLanguage.sUrl, null, function( json ) { 
							_fnLanguageProcess( oSettings, json, true ); } );
						bInitHandedOff = true;
					}
					else
					{
						_fnLanguageProcess( oSettings, oInit.oLanguage, false );
					}
				}
				/* Warning: The _fnLanguageProcess function is async to the remainder of this function due
				 * to the XHR. We use _bInitialised in _fnLanguageProcess() to check this the processing 
				 * below is complete. The reason for spliting it like this is optimisation - we can fire
				 * off the XHR (if needed) and then continue processing the data.
				 */
			}
			
			/* See if we should load columns automatically or use defined ones - a bit messy this... */
			var nThead = this.getElementsByTagName('thead');
			var nThs = nThead.length===0 ? null : _fnGetUniqueThs( nThead[0] );
			var bUseCols = typeof oInit != 'undefined' && typeof oInit.aoColumns != 'undefined';
			for ( i=0, iLen=bUseCols ? oInit.aoColumns.length : nThs.length ; i<iLen ; i++ )
			{
				var col = bUseCols ? oInit.aoColumns[i] : null;
				var n = nThs ? nThs[i] : null;
				_fnAddColumn( oSettings, col, n );
			}
			
			/* Sanity check that there is a thead and tfoot. If not let's just create them */
			if ( this.getElementsByTagName('thead').length === 0 )
			{
				this.appendChild( document.createElement( 'thead' ) );
			}
			
			if ( this.getElementsByTagName('tbody').length === 0 )
			{
				this.appendChild( document.createElement( 'tbody' ) );
			}
			
			/* Check if there is data passing into the constructor */
			if ( bUsePassedData )
			{
				for ( i=0 ; i<oInit.aaData.length ; i++ )
				{
					_fnAddData( oSettings, oInit.aaData[ i ] );
				}
			}
			else
			{
				/* Grab the data from the page */
				_fnGatherData( oSettings );
			}
			
			/* Copy the data index array */
			oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
			
			/* Calculate sizes for columns */
			if ( oSettings.oFeatures.bAutoWidth )
			{
				_fnCalculateColumnWidths( oSettings );
			}
			
			/* Initialisation complete - table can be drawn */
			oSettings.bInitialised = true;
			
			/* Check if we need to initialise the table (it might not have been handed off to the
			 * language processor)
			 */
			if ( bInitHandedOff === false )
			{
				_fnInitalise( oSettings );
			}
		});
	};
})(jQuery);

