From b8fa199fbf9b40d7a5a6482be1e32759310ff10b Mon Sep 17 00:00:00 2001 From: Prafyl <86287866+Prafyl@users.noreply.github.com> Date: Sat, 24 Oct 2026 01:36:53 +0545 Subject: [PATCH 1/2] Fix reading of objects with custom streamer stored in TTree branch Assisted by AI --- modules/tree.mjs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/tree.mjs b/modules/tree.mjs index 223a65b40..940c058b7 100644 --- a/modules/tree.mjs +++ b/modules/tree.mjs @@ -2142,6 +2142,12 @@ async function treeProcess(tree, selector, args) { obj[this.name] = res; } }; + // object written by custom streamer of the class, it has version and bytecount + if (branch.fType === -1) { + member.func = function(buf, obj) { + obj[this.name] = buf.classStreamer({}, this.typename); + }; + } } } From 377f8c26a9b138b8c681818f213d0123deda3b3c Mon Sep 17 00:00:00 2001 From: Prafyl <86287866+Prafyl@users.noreply.github.com> Date: Sat, 24 Oct 2026 01:36:53 +0545 Subject: [PATCH 2/2] Add test for TTree branches with custom streamer objects Assisted by AI --- .github/workflows/jsroot-ci.yml | 3 ++ demo/node/tree_tobject_test.cxx | 34 ++++++++++++++++ demo/node/tree_tobject_test.js | 65 +++++++++++++++++++++++++++++++ demo/node/tree_tobject_test.root | Bin 0 -> 12965 bytes 4 files changed, 102 insertions(+) create mode 100644 demo/node/tree_tobject_test.cxx create mode 100644 demo/node/tree_tobject_test.js create mode 100644 demo/node/tree_tobject_test.root diff --git a/.github/workflows/jsroot-ci.yml b/.github/workflows/jsroot-ci.yml index 8d0a64142..3305c935d 100644 --- a/.github/workflows/jsroot-ci.yml +++ b/.github/workflows/jsroot-ci.yml @@ -70,6 +70,7 @@ jobs: node demo/node/rntuple_bitset_test.js demo/node/rntuple_bitset_test.root node demo/node/tree_int64_test.js demo/node/tree_int64_test.root node demo/node/tree_lzma_test.js demo/node/tree_lzma_test.root + node demo/node/tree_tobject_test.js demo/node/tree_tobject_test.root node demo/node/tree_draw_range.js demo/node/rntuple_test.root node demo/node/rntuple_test32.js demo/node/rntuple_test32.root demo/node/rntuple_test32.json @@ -192,6 +193,7 @@ jobs: node demo/node/rntuple_bitset_test.js demo/node/rntuple_bitset_test.root node demo/node/tree_int64_test.js demo/node/tree_int64_test.root node demo/node/tree_lzma_test.js demo/node/tree_lzma_test.root + node demo/node/tree_tobject_test.js demo/node/tree_tobject_test.root node demo/node/tree_draw_range.js demo/node/rntuple_test.root node demo/node/rntuple_test32.js demo/node/rntuple_test32.root demo/node/rntuple_test32.json @@ -249,6 +251,7 @@ jobs: node demo/node/rntuple_bitset_test.js demo/node/rntuple_bitset_test.root node demo/node/tree_int64_test.js demo/node/tree_int64_test.root node demo/node/tree_lzma_test.js demo/node/tree_lzma_test.root + node demo/node/tree_tobject_test.js demo/node/tree_tobject_test.root node demo/node/tree_draw_range.js demo/node/rntuple_test.root node demo/node/rntuple_test32.js demo/node/rntuple_test32.root demo/node/rntuple_test32.json diff --git a/demo/node/tree_tobject_test.cxx b/demo/node/tree_tobject_test.cxx new file mode 100644 index 000000000..1218f1532 --- /dev/null +++ b/demo/node/tree_tobject_test.cxx @@ -0,0 +1,34 @@ +// macro to create tree_tobject_test.root used by tree_tobject_test.js +// root -l -b -q tree_tobject_test.cxx + +#include +#include +#include +#include +#include +#include + +void tree_tobject_test() +{ + TFile f("tree_tobject_test.root", "RECREATE"); + + TTree t("t", "Objects with custom streamers"); + TLorentzVector *lv = new TLorentzVector; + TVector3 *v3 = new TVector3; + TGraph *gr = new TGraph(3); + TDatime *dt = new TDatime; + // classes with custom streamers are not split by ROOT + t.Branch("lv", &lv); + t.Branch("v3", &v3); + t.Branch("gr", &gr); + t.Branch("dt", &dt); + for (Int_t i = 0; i < 100; ++i) { + lv->SetPxPyPzE(i, 2 * i, 3 * i, 4 * i + 1); + v3->SetXYZ(i, -i, 0.5 * i); + for (Int_t j = 0; j < 3; ++j) + gr->SetPoint(j, j, i + j); + dt->Set(2026, 1, 1, 0, 0, i % 60); + t.Fill(); + } + t.Write(); +} diff --git a/demo/node/tree_tobject_test.js b/demo/node/tree_tobject_test.js new file mode 100644 index 000000000..b6f289f5e --- /dev/null +++ b/demo/node/tree_tobject_test.js @@ -0,0 +1,65 @@ +import { version, openFile } from 'jsroot'; +import { TSelector, treeProcess } from 'jsroot/tree'; + +// test reading of objects with custom streamers, file created with tree_tobject_test.cxx + +console.log(`JSROOT version ${version}`); + +const nentries = 100; + +let filename = './tree_tobject_test.root', + any_error = false, + err_cnt = 0; + +if (process?.argv && process.argv[2]) + filename = process.argv[2]; + +function failure(msg) { + any_error = true; + if (err_cnt++ < 10) + console.error(`FAILURE: ${msg}`); +} + +const file = await openFile(filename), + tree = await file.readObject('t'), + selector = new TSelector(); + +let cnt = 0; + +selector.addBranch('lv'); +selector.addBranch('v3'); +selector.addBranch('gr'); +selector.addBranch('dt'); + +selector.Process = function() { + const i = cnt++, { lv, v3, gr, dt } = this.tgtobj; + + if ((lv?.fP?.fX !== i) || (lv.fP.fY !== 2 * i) || (lv.fP.fZ !== 3 * i) || (lv.fE !== 4 * i + 1)) + failure(`entry ${i} wrong TLorentzVector ${JSON.stringify(lv)}`); + + if ((v3?.fX !== i) || (v3.fY !== -i) || (v3.fZ !== 0.5 * i)) + failure(`entry ${i} wrong TVector3 ${JSON.stringify(v3)}`); + + if ((gr?.fNpoints !== 3) || (gr.fX?.[2] !== 2) || (gr.fY?.[2] !== i + 2)) + failure(`entry ${i} wrong TGraph npoints ${gr?.fNpoints} x ${gr?.fX?.[2]} y ${gr?.fY?.[2]}`); + + // TDatime has no version header, seconds are stored in lower 6 bits + const sec = Number.isInteger(dt?.fDatime) ? dt.fDatime & 0x3f : -1; + if (sec !== i % 60) + failure(`entry ${i} wrong TDatime seconds ${sec}`); +}; + +try { + await treeProcess(tree, selector); +} catch (err) { + failure(`exception when reading tree: ${err.message}`); +} + +if (cnt !== nentries) + failure(`expected ${nentries} tree entries but got ${cnt}`); + +if (any_error) { + console.error('\nFAILURE when reading objects with custom streamers'); + process.exit(1); +} else + console.log(`OK: tree with ${cnt} entries\n\nTest OK`); diff --git a/demo/node/tree_tobject_test.root b/demo/node/tree_tobject_test.root new file mode 100644 index 0000000000000000000000000000000000000000..0c013590c73528d87ce8b458feeafee9a3e903cf GIT binary patch literal 12965 zcmbum1yEc;*DX4DaJK|^cY;fB4GzKG-GWT{maQ|fndjkMqR3$8JOmSSCOilG& z?2W8Uja~FzOr2eroZiCvf2#+;yz%_y0sy?hG>YK;FCPHF3jF^H5@sLj1ORxS_SgQc zpZ~Ut|C~+WR7DJ(txa7ZZN5Ud0H6Q>A8-rQ5%>UhfiD8kl6#j914w%haEFLawYI~O z#0nleZxZn3Q}(SV!xC4*iocBvLO_EI>b?u_F&JdOagWm9t3iIz97Sjk(i2IUemiT5 zq{NACcqN1pt29wg{hH&|*U)fVsHk;23psYx{fv6P&bb821zx*dA8t9p+{ZkE1HDNvri9`e}TZTdx~fn-S>x0~2s#iMxqw3E+m@&7fRo%J_^VpTPScf0M)b3oBs zr;hHCJYB!VRrrTaH}Kp#34*ji`Lr*Yc+M{xYMr;#5;=Q0TRzg~b$0ujKhEfzRQoYs z=ZBp)`4xS6-o1_w*KIniU$oicc}%v2)@XWjEdV8QTyNa2#Q86oqxgZ0LmOIXzH`G{ zQI0_3pI2>N99I~9%__dS4G9C7x$phTn+TeYgB_A}I0owM7zUd`3V{yV=@tp73EHX$ z%`2k^o)P97JIz~RX`qGiVYm^-Qah~OpDh+;K8qCxcy2EqXq749R2~z~;4l`PK1{VX zVeC!Xm)V(12uj(ByVjv~Wk(k0tlsM)VF^wkcUH%2zr`hKjMZ|Fuk0_jO} z?mVe7_Bp3Kv@H=erogEx_>qUIn&ZatyMbBkbI;%}@|i47K)n)TOSuSMp66t>0^*ovBe}Smi#pn~yH6 z_I8Yd>6aK6vu&Bz!9yl+|D!6;`Hgc+j#cjQCJ5|Q@|uMG>XTEsHXrsS+*>~@GntJD zPH}q)&GMxMrOtxsfTxB`OJEY+#g5iZAPu-nTdEaJ!F*umQ|Ap|{JqI@&z%_df1!+c z_+^1Zc}&I|YS*w$iE2;P5pPgJ-%9$hX9av_zeO%@ix#<;ouR0@kDxo20F4ehZ`ELf zDPVR!M?C|5GhMx^D%xva*PCoC+p5~H249$Q61J30H+WcYavyW7+M_{_WsJtJsov1u zYOY;mooYDpK0;@iWuLAdgDsAgIYz8=j$x10kKc&Zz}ZIq=J11({G3ulMZh9bh!afm zv63&;Ggk%7foNG(?3wnjw(~TM$FI=S>_q5wYq!-^HU4HQ>RBH?VM|?ru0K~dqPX)l z;;>0glWVHdhW|EO=QbwL0r*DTRN%Re%?$vhotGxiZj9Jjh%_jc*@hNjlieGvWpw$GV?t*w`ETQFcC z{|8W8|A4sl4{%%mfV%Y;LV*9^ozOo(3;hF<&_6&5{R4u~Kfnt81B%c;KnVSX{x{~H z)NR>|#Q#9$;veQN{(=4CAL=gt!TI7JKo|djc<~SCJ$$#Qz7RV<0RU*#H;qT<{+}8T zJO5uA|CRl}H6C^MP2=IA0o>?djSo)dlhe__5|6D(3#GYqzovijWBqpQe5#W z1effuI)=a{)fKONaH;F6BZe2hOZDbExPS^0_>;*p(!2xoUb#Q^&L3eI2|Kr*xj&u@ z`f4}ov9ypAb*G1$ebc&XsdhuvKuzBHdcRqCW3pa$B5$c6p+kMYnFKD~IPY)Nb4n(l zFR8rQ)_-u@Z_l(m1d||2>&O0VP&p0^VBR_0tz)MIkTkJUu< zC9lp~RWCLo)tf`GpEJb!&Gs9UdU~&^dT@#Oev|!l#Qn>PSET^HWA4apjKD-Z7gK_1V<^lNZ}IaQ^;r_1mjx zLSFUB!$B zsrYtdziw^T3GgD0e1oLa;MNTwVR_50_7Lt%Z}(mOSTr zT_UmcMk(E`G(e9l%y2`-8 z@+>#|%<+UMcNAR043-X8+nQ-*n0 zNJIC>Z7ib36V+@z$yWM*Ox;4IBN^gd zYei8;(pS8UO>)W;7;6>iKZ|fxdc_KtoI<-7bzE(13 zLJ*INu~uwx1cisf+)QI|m|v4p+axzD&4*iL)g;{}PS?rgW(EzPTA)^g^U*!Y zWdvb`!t3dIt?OH;X$V+Tn%*=;^5}nR3L?#aX^Oeie`|_F!kebpKm(2>!J4wLE@YFe zMlg8!gOM>ztA@--ygve)UY$nqyYS`jmIb8jTJ!R>-*N5~m_znJ+?K4z?Utxt z&ULR4dHl12D)`7Puxf2R{a)X;21s~AV2gUPqE*k?#fLH7zFI~ME8!cvB^=**VgG!K zcoVYpQvXx%aRjK(vGu~g`s(3!JturW@p#G8^>ORIEAyEuf^)?9^=qS9-N3Q^ajR8! zUAnHNF0;YSVl<@L*CN+M{}t_~cWEohi$oB0O?KEUb@UY-kY@H9R0d`mT944Nn%tHxsEhVD^>nh(!NzP{;k9TSBApyf_nr$dL5$& zOoAf?A2)f)|F)xp+a>?DgO6Uv<-+FRgs6Y7Ya+rtdW#wp-0u9Blm~3&sq_7<13KXL zNkkWL|A3KUjYHl-W7muKBnFN7U$N^aQ!TSyfpJmwO#PG*R_s{&R9(|zWPNDX6gV<1RLH|J`ig7iZi0mP#iHi% zM}Dtk<@lR8kOw%})NlSRzh#bGS=kp(tcLff0TiE z^P2k4>)4xDAUL_YD1V8^iu`%N;nO>&-+J6ECX=0#0zu#`RYF5JFC;G}|8tA^uN6~# zwa<@#>ZXr)C2EBqR;v=S;>XD$i|$9R05xF1jc=ycSMqe(ZmY*!yq~$t$Yzxg3fYa? zqb_UfusNLoes&8Gn)?mgv?f%}adityIiE3C#IKqbC>LF5J-kK_=<+Vec6W^BX-j7) z_g`5X^Tkt#Ocz&8!na8i#NG5lfJ{MXy$u6r#O0?VTi5PG!RiCP?Q5RzKNb$Dx1$vT zDl%<-AvT{7jtFb-k=>8I478!#y(%WAgts)ev-L9d15Wj!&SSuFKU6`UXG4BV0#A9#stx3Zj%G>s)Pvq5zw*mu_?d@*Z(>2BWgh2Sa-mChw8pVUWX$jmUUz2 zp|cX_89@jFaKur1H7C>om!C5~bocWZ)J`4Rfp8yi>ptgUfKlm z0#r1|j2vP`{mkbi6y9I?nM9ty3UVP}@r}~ZCGd^2;HlD$GVsmPjdNgLuI=9$LM0o9 zbHk@-8YXitu278NtesSk3BnO1kWHWuWjT!E19comu$R^y#t3iuFNQIv+Al^woO@r4 z;BGx#d=QbKYQa*L33~tDf>eg<8Hs(81H^z~{C`h)MEe9m;{Ajp~Bzk4QCQJOBV|gV^?Pvds`A`@XUvyt*MhUILp3}fFyWu zmK)TktT8`fz1&Eya)^J3M8MH@LN8Q@hEf(gQhahP`BO`SKx9;FtCdk~w}^!x@)AVU zakD-0^Go}?7gQb(#eSmSMea9>&t_&r{hXQ6ah*1Y9qnA8PA+>vDtea2$U4Zhz=7D5 za_rB{LfW@uh6U9fIO>Q8xY5;m^c=n2%nMK7d+s%tZ!lXzZr<5+-+!iulYGF@Vo{zM zyvbX+|W=DgU0^rfX6*b%yRK(uo6vrA)llyr4W8E)H>l4^w4#3K&QZ^!HJ#{DTuXYu4gv-dA;>ndg!#`MLWcLIpDIX)ey2=td$DwPQv-eE?2R zS+mV)TSG?NWjk5)YazK3bCmJ##6~taNbV)th5l0fFX3=lcu=VGL@7_RGUjk;q5N^0 z%LIIUfPBJfmWJ6RvP3ELZWn?Pb@u##V>Y;r7z#dE|K-ZW6v{{W-mdTjejx#;RqZl3 zeo%JkzPkXR)90lbPQy(kc0+nN>Jq0sb3VpMstrywL%dBBtIu>eolrOAnAMR?lxS#5 zo->LbI#RIsGgUBM243+1M^TzY7=+Z0r>JtT8%axfpLb?2Q#kh-~O%T`Q7XZn(lXlrjsW{?B6^-%3i|g4(LAPlN^mArhgN zW6zGSlx6|e{uwtu;ayJ>}GPV2lt2rijX32ZTiq<&sqnFbBqcHK@W6jZ6# zzcAdga)Rzk&b1@|+*U=TP?X?6Mly}TJA{^Z7ypQj!12!cKG|=}sz@p(3EKgwN37MV z3A&qC9sF|#@5>*=?&y8TxV2m)WFf%8>jRCdY)SeJ+F*q~K#(5ETW0#1*P@T7@dX7$ zhPpWJ8JwWee-w;H&JE|l6xco_iwg-G12ra$l|H~`jz=#PN(u;kYeyPl5% zTlJBRLTwhFF+9~Q_1rF&H0z&Y;bag+iw5#$i%7jf(ehQYo9BgAo%KAZ6bw}0=Me3xTWbMoZe~V z30-GtHXKBj?;Zw@lhpS*8BBWletuVjV!j6mcdF8$I1n64TEpvuJTOX$obo$3)}CSC z=eb5&4Rk3T(3)jku1Bl0xPYx(5`OxD)v!s51RyZA(#4GxerL%2<6@SH&B++g6Pp}UxjOAoM{k=fMaQcu*chfxrdhy$4cp1 z@=#vWS5s@gx$WhUwnN%mw|#h6onq5rZsMGj1cuvZtRamGp^Lu=7(Em}_Q*?)1>>(# zGcpjT-NvL%1}^s2dyU{2@JwQ>Nk^(`Rx#-!#h?Rdgbk`mkh}$Z-I0E|8(3|+eC$0S z2#XR=ao{PcMi)8hk&f)Hl?>GZz|MSUBuY(qtw&3aIJ)~-Nx$vT%7pTxiU@^RR#CEY z`n^XE8Y~(EyT_o*l?&hAfzd~o4kMPr)yzEe!wnJ&I-Z%>L;t|*?)nIxI)Y`vip(Pi z)7l1es+kFr)c}#~*O;G$aZ?rqGWm`|8J=~WYIyV0X^})2(*(!gvg{|?1~lii94saB z=aSL`248TnP~f5oKyHzViV>Q@M;_T$XPj-5$b$_tkKm6`5dYQo) z_YlM{NA68S!38x2p-*f4@mw(E-#_f-fj7?S{D=ajoVltVKfhIvbUlKFW|hq~EAoVQ z`pM@qQ0w1(TAGTBqU}5uqD!hXwf-8rQQn+ zaI6xbQc(t(^}jRCt>0!YzQm;{I4>_!E@W_ukaVnAGIbn|M&_4+T`T9S9P9)KdG$0*qbMGw zy>q$=Cgq*zqZzQAEk6#?eBX$Oq+ZsE6YW90h^@~}&pzfEP zNi%tEvT75xPI-pmaEC&cffV_^4(zvT{K#&r50C|gi5Vz)A)bwXWwqA60HUXc1B=)cWJ!xzRb%Y#rv!2YF!_h|eoLSVfgms}N2v{??ewfEU>s4cX^5 zbnu`rIj-gZ^W60;*1_5^J^t8Cpz_<4XfB%9%cWYxRYb)e#ZN@#yhe*kT*V#-IZS$~ zTgh$V!4e({FjdPWcPH$Wy(xu4k=|r7s#+w>Q4^Q%r6Av41g^6YuN|mb_ z^-RoXkBim-j}|2TjZ(P*tezyqVRv)-hUv1lhA-u@d4?kO33>D{rw1BC(}?n+x%rF} zIm8QWgThcQR9Dy|X#`wCeNfBzZs8vpkDSIfb&c(M%C&asWtZgLie3@?@QG)le#wDk z2EIAO?8vjX2TlGSc0h9RCe&YkB@tKEh?@HI^oN=i%R3xU#8SJthfSq&4kcII=`Kft z87)o25TJVgl>;W(vuQ!u67f3c#dTcdsViKQzm&>`2I-<<_$rhq_;r3}hhSp$-n+W) zeQla!WKZ{I4WTI2s}ah{hc&}9{u-~}`KQLoS=1{w7B`SaI2WjUWs(z^c|X1av055@ zek`|fI)9D3-oQ9OG}(S4cF?`C7>nAqpFaqFN9QZ}Rjm`o5$F!^`PB?&9=6ZZt{8gz#uaf16(^P|71~u!V6zaT z{N(kb*cz1>N5ie+*6EC3jVY{^#Gp`rdYH7v66!)6B8-uAWVf~7BuMx7oEtfk(=Lhts_#X+&YGKxP&0V6Kv6{6Z^!E zNJgbRTr>yhE|a6g--DP|Ju-4{cXTM-2eYFf<_98{J<*o(SuS{n{Lkkhn)uR^K#vc` zbMSM?RI(c$b4VsMq%l6fWN3DHB+>gixr3}d2}BB6V|H~+qh_P~OLBe=(_E_bc5m>< zI_eM?;FNdzEcs|+Yru2%|?6nUXvWMhwjjxXjk(P!&tXXa>| zo#gq?)vKeM4LWNQQP8Z9RG}}Q>X7!N)$}B3Dd9upAyOY)iCF?}Ht(j%JE&;f`7gOR zuxB{wN8#75jBGsvmL`6&cY;Q}Tw~4EIyyT;ih6iTo?MZpJvTm6)Rk3M=+_W;7Z>P% zqVxoQ<8}+>DSC+GH9wLRILQHDwL&k#CXe#<)kc$(W%xE10_#xQ#dDga0#c|$y zXtSmu8@hG$GeIEL33BShZ!QUbrMJo_<-Z&ZzLHh8*bF?}$8VZ%#R=(-KcsliCl+JT zv(d-D-Oo0|M410Ul6+}%_s_y8QyR_26D1@XVU2l`1f&GKt~6eAA{r)>ep}z(a!}F= z5BnnJq!QW}52Lnzx~q*zd0NdBwyXlHcO2`?8fo6UAi2&A6Sh7Picm+mYpD;_j}NMh z*UFG^@Av7P;ul0c;WJ^Txin76q+5q!Byo}DRRLD#Wh|Sgw1aI_X5tb}-0VsWc|&LQ z7LU{%uyboKoj9kQ7Qz;KCdWs{$sCt`o0;`F{gM>+6_ykx*s-4xpxlPO^kS9< zdi8}5ZG!$;6UhH$SR^z2^ig(jof-9?C%B8Pt8c!$;8-wM(6gQeU| z{cha~vy>1YS`o<K|F zEJK`@puDZgBKgOUpVz8~od8&iSX@dC5dqFC#Fe!{k4ox$0!ioe@IKxWtarF=odv>8bUcQfFAKGYmPaXN zMQ1q0upPJ*T-x%Y5E@(~jc+%ok_nSffEa}hp!sPXOZ{BT&zioql+*)0L!{!AZ(9i* z9RL`Xu>$DadFZmnI3ZM4LWC=AZG$xe8^7uyrxYuAd{X2^r>nm9vWSJryKZYh6hTF1 zo_I29$Oq9Y6+kpm0rgy<=w_!yR4&&;j@iXML%=dSx>fE>?@q6?RMa z3#Ze~akdvv(NEI+1s5JUk%boMNr{8qt}EmyKe??skFMes?FEzt@#b6c3>YY&i&&)^ zztxL)CQ&*PY_9M=7>wPp0T#3gF+6{0ph$<5RVJne?Q^B2$}VyFU}Rw7yJaZs=awH( z1PS)N4-Lflh%-^n0=Opl*$@;UQ%2$CJIgc~;{0Jh7zPS;0`jVq0y{cTTMa=vGXLq* ztet3b5RN9T606wWg}SL`Q;;N<4srY?n_(V*tTazwVN!})zGAdgcuh^YKC!Gfq}HCY zKJ#jFHE3a2YP%}7=*7uDyk2TUJKlH3S3UgtDWBDGXFK**6i?Zr6Lw;R+3M#)7=OXs zE3WSh+S&?xh2dp~nRQ2+&S!hM%BXC^m*ED8IS4AViS+`VSgZQy72U7MS{ zH>QKIf=P_V=C-mStf!J?7bL8tJzBLJ~!%k%a?ObKk-S@cg>=6?yLO zJWO_*(3$*H;)neTh;9w_y3f%4_PK|ey)L#P_RW{GAo;Ia)a&IiT`%$Ew)_Bm4*BO^{Aus?nfKNjFPvYm zI9fOSCG^FOk&4c*wV2kUV8t@@@%3ZNYJGLj+eJE@JBsqdRmWZ9LO)|f9K=jY&nIuT zyedTm`ZSZJM&Jeqw3&NA$_?;-nl45&K}KQBr+T(a7xd5kE>muPL@VCTpqeywr_Rb3 z`iTEwP#~EXOJ+DPjx<~^qxY96!Vlb_c${7Q!Cw{_KiKWU9b130W--7Ot!aEy?hsSX zhwd0kZ)95Kk@!7m<=;aWswXUzCRc7o8-TWoah+7cqqP?W9P7z~EKlyj+}4eGI=wO= zN9?Y?W)Oz_8VW)K?mtMYl|E(hply4FgBi?0aebx3`Ew>*83l1h-Gh}-e=&`*mi*KG%7F9~JtxGKal{xiJUL_kqerDc zeyn=|yBVK453$qvEK^Xv3`Uh%w!5pQ$Tp4@F5Mh zHOgg7TFr`fvHL^*qc2Pw)cGOg4n~crt*i@0Og1NbjZ}>(o>^mZwZ)88;^v#{IS_wu zie<1fhE;?jzBwn)UU^}OY+|>afAI?VGwcS0aO%doiWqf+PI$@$d^hVB_O#NZVQ$3% zM`G9bphgcd-Ued>Yn9@kt0uT;9Tm<&N;J>fGAVr&ZDhyQ&iFjTlPKC3L~y=St9URP zAz_uY7BI8Ht)Vm9N+ueKq58qfMeRdXf+cBZvBPr$cv6B*10wRItH=weN zNanKoSEj))XVzK4TgvUfm%EB1U<7-YyZV=N=WEU#sfq_}ZH|IDx!G7vd4Kasu4}$t z?W)xSL6L3w>A~gX>PRhc47Cj5`$<*?O_)oqlEkO{#6**-hQ&1yh)y1O8XZK`hKdE% zGU-)I;sI+%5Z|M=`}LXqTxJg4KGWr|)FCWW8g4V0d@FSQMnYgK+6T=yq(fY^>!hD^ zN?x-6X-KJ%6Rx5J$x78hQG12~r}~&jwPNkME6;f5SJa^{15b~@Oqf!)$n7|?7b)AO z;qo>u2DB5cc|(aaw6Tr{hN>(ff{f~)==!E)TuZs|dI{LeU%C0HlOv#M+>+ zVPTAF88d$#R@iiTVHjAFSM4hUPw=^P~reJt2BNHgzV8*u`p zNQA!MO9(Q7H#ZBL?FP!ibgbpZ;!#q>A z6eRb@7_oAAhx)NK^k0|JsIDMthlzi7Tf?BlcJBf-I?ra(qgYi};PL*Z&3}(L{zP(7 z%cLHW#j%X$Lisy@O#b_yOoSrp;DD~yW*4UX?}^kej%)aFTjSq#=sqEEE5S~hT&RRZ zubAP@C5Ar|QUtvglI^Mc(X=`?G3JC#o62&gEMNWFFtJmDK9+tW6|e4Htwy!}^yGl^ z`_Q`iGa^(j>J{2gB_&cZxIfGt`Vji!sFTQX18l_^!r+n;6RF40NaBU(bDszSJ!u; z@NXg3v;36YlBEjaLF8-E*VwxfZFEhST)r@X3AinZ9qiMc@J%=XUIDj;lCWbkJ_k{t zp8DQ3_mHBc783jo>;p*&r7zZbnI-J@7{c~rP`rdDqHLcFgKc*^oN+L)FOV<>aP>l1 zTg84Qss8=xCgIokK8o#QSm{K7*)$~K7b#a>3S|e~$sHa(FCx=mpBdbIvHF^lmSs8# zJ!JqeD4-3$Sd>h!PU7l`Vo~=lD05zIO$amKqgcU8mnVF=SiEi|J8PrbFLlh;n0505 zgl{)rA}(Zd2aU1K=h0#j$)hKn`THw)f36XD2FS-|2$6P-7hi5_5RSi}9~#j>M^8d# zFPBhSC&^8h|K_EKEOARfGz}Ms3>wDED{=SRrKXnjPe;RJAnPW-g%= ztx}R}o+|0tuOYe_9k@`pC8fW=uqn|=@SB{S`pwrJC{E9Oa6BK8mqc4}aFOrvX`$n& zKND&3sN`g=a)(S*Ujy~S_7-RXp$b}{3M8YbYpndm80$*pmtn~Uk%5A&@KjxcY_~+* zx1&qhxN83-5S>f3Ugj=6t)C7ao?pxloBZncR*JzBP2&1AMgwZ=Pl8DFLdT?cEE;Ih z3J0Dw$NdkXGP+-zhC9~Z4_xPC)zkX1ql$}nGXgq{a%8c`3~7zBtC+=6f2nw86S1_L zkq&s$iyFyFRe{iGY{m@j!UB^So!JO%Wb=Gs82`BbQfL8Q@C z&!E@4psks8y)%^iGN8N>*EX#hb|x^&tn?CUG(zTv>&LAC3<&jE&gg=wLk_xL60R(}fBx z;97`1>y0_26)N}nMI7rPXtzyP;^*jPjV%fFDfZY$ri+8_0}&#D!4Nuh4CCPr`kav- zzzBR%eA{p*9wp&hHm5XVMLdBaJb?*3`QI+Tobm|{10+~lcaf-y8c_ndA2tVrwl$0m zp4-mY3%;U5e-%6tmkcHcXNlI-DhsOM*@UVp_1hym#48cHaGLxRzU1KFAP zH{FJ|OYOGCvQb3kx0^_iv!AgBJZygtr1n%}y}(-Iq+ifNTTbioSu{{$63GOg0)^|h zKX(Ed(67V}a9cM&6Zc&bIkQ=k@}`uk^O)3E3>%7djTBU0r|&%^($pFk-dBXS-yKmF zbP2~ks)|>-4nafs`2(FeNvITkMv2^T=W%eFsh(`(YB@Cw&&#e2Zz`=Kdcu4~lzN5t z+NSZMEH`w=(oh1(pJjKnNeL#$XZm)+O$_L-NCx=Ir~>wwg#Un)fu$_)=7S^ZBpG(7 zav0cb?9u>@dEZfZcx;RZK6~rl9pP_ec+YBL&8neuBsK|0yDK#7YO-I*0&mn!r;W;} zjqII%kOr?ATqQbg(d=_AguQBmp8(%pkq6vM8%fFRNim142(nHV7K{gXAc;YB|Kz8> zFKj~G(0d;h4&IS;2*rwI5H@9+g|s6-#U^0l!ed-1#_^`r&??)dua%;rc-ly=Io`cX zDLuw+yhFp^F+yralY&en682kWjP}Mw5Y@bwmPK=PDKRv?%uPLhp) zuI*OuY~rbzz^asY5a}qqOQFNgU%s84fs*T+7OKqu$P8AxSq6*g{(~`Xz?6STuy8{y%6kA_s0fO1)-vIH=^< z80JnCv-H|j8;;gw8JD?uq%yA2ex~`h5i9T>ClT5aw_U&!=OY5^yiaP2-_tJ#v@#m< zJ_T|l+Cwy7ag}*l36|enmFTo6#wDAutKU4e9{`(8Ffd&QgNbEaxxk;+XWF`cEdOCO zEDUxJrv8*)oTox5_zfOqIzfPhozyDP)gS}vrhJ4GLn*8;3v|^&!`wu{DGJZjtrcCN zRGb4@zT7#~TWG(vh6%#)Y2Z@H6oiG6-RGb%7=o_7 zq%2giimCd&;=UBs!3o=!rk?2>I zzVa%nZDO6|LoUnlt6j`=OrOAQn!!A~((?!2bfkET6dm literal 0 HcmV?d00001