From d6efd52b4b1a2cb0df649a126ed507fb9f8d470f Mon Sep 17 00:00:00 2001 From: Klaijan Date: Tue, 3 Oct 2023 11:25:20 -0400 Subject: [PATCH 1/5] fix: isalnum referenced before assignment (#1586) **Executive Summary** Fix bug on the `get_word_bounding_box_from_element` function that prevent `partition_pdf` to run. **Technical Details** - The function originally first define `isalnum` on the first index. Now switched to conditional on flag value. --- CHANGELOG.md | 7 +- example-docs/interface-config-guide-p93.pdf | Bin 0 -> 104337 bytes .../partition/pdf-image/test_pdf.py | 14 +- unstructured/partition/pdf.py | 131 +++++++++++++++++- 4 files changed, 146 insertions(+), 6 deletions(-) create mode 100644 example-docs/interface-config-guide-p93.pdf diff --git a/CHANGELOG.md b/CHANGELOG.md index 7324722691..a37fea2809 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ * **bump `unstructured-inference` to `0.6.6`** The updated version of `unstructured-inference` makes table extraction in `hi_res` mode configurable to fine tune table extraction performance; it also improves element detection by adding a deduplication post processing step in the `hi_res` partitioning of pdfs and images. * **Update python-based docs** Refactor docs to use the actual unstructured code rather than using the subprocess library to run the cli command itself. +### Features + +### Fixes + +* **Fixes partition_pdf is_alnum reference bug** Problem: The `partition_pdf` when attempt to get bounding box from element experienced a reference before assignment error when the first object is not text extractable. Fix: Switched to a flag when the condition is met. Importance: Crucial to be able to partition with pdf. + ## 0.10.17-dev3 ### Enhancements @@ -20,7 +26,6 @@ Fix: Updated code to deal with these cases. Importance: This will ensure the correctness when partitioning HTML and Markdown documents. - ## 0.10.18 ### Enhancements diff --git a/example-docs/interface-config-guide-p93.pdf b/example-docs/interface-config-guide-p93.pdf new file mode 100644 index 0000000000000000000000000000000000000000..db41a7cae4a9ae68e4d4445e61c754cb8b96c10f GIT binary patch literal 104337 zcmcF~1y~);k~R_`Kmx%fA-D!T2iM^45Zv9}-QC^YU4mP1cL?qY8r=0ikT3UsyLaW^ zeVzq|>FKWSuBxf2uI{&+Sc*@8ijEoxN8J76^v8a|S>||mFB}k$7SBT47>=DCk4C`A z)K5Z z2Zr(!uJgC?5IP?%`(CITOnt5TW%wx$&ATV9QCPiv83nB363e7-^y2cnNLUdlNH>9E z4-0PJ9~Ry&e^BGHi#c0nKCim_wSB0u>|xcnzB2Ciqsh|%ebw!L^5NmDN`1-wI&VAl z_f~hOn_HEDJ`FCrr^83aH}3bI?Z-pMNKXm(eTdhaf=g+WP*-QP%&PAlyYfQZdyl{| zjw#Yz=XYE?`PqLEn0vljUD`d>ch(_|QHfJH_U(ILlogi0i8iS^C28%`8?ofc6#?Vc z&y$&T3L|`dz5au1d7rD;3i@^i~mnwYI=+uj+PFKOn*7JS!{;bieZTmLwNS6_U6A zP@oJa63$#@YNfp^&>ipYMW@l;5$qm<;_aaA{&eJV4K|m4yh?Nkz*O7rqi*2rwadZX zV?Nh8UHFV^u;caXYVDkyALH~L3E(u#r0F!u4;AJUy1wEVDYZJ^U^0Aw&`=;kI|tbD!FiP7{(W~x9fY=Z~+ttL;PfGsR75(K^@@y@vRd)VPU6qy-&%SR(B6L zfNS3T*haEj41`2(A##&5u{?U@Ys#dd4H2P%R0D`pzIFJz6E5Nt-r;uF(vGh^QwP0M zWZ_|$QK=px3c+WDbcCOAQ@PZfiaNQSLUvWG9?N&{lkN}ir$Pp3|!EjeUv5A1S1j&D^z{}!k$D;O|1?%I4+-Yx%8aBz4hs4Qq94A zFr2D*&#rb=*5aleqU4t!aEEV}xHQVLdfF^2>nn5dy*sJ1){gqI4_! z#y@;qW17%YAx5+XW|n!c)nZ(*)Cqxm^}WfIrj^eE@gsey_`XZSwAo++2FmGl-1b(LI^A>hQ zO!Sq`6XcU0wV&Q9@)*>~RxTk{>$*3+9DF-s(KRzeBvj-8tb|9;(#>{5&62(`x)(Rq z7PttKOjE3*w;0C`)7u4v+BrrdbSvYJd^HC!Qb{e~Gel~6+~}(m*77FYPMNxw-JK#yalc=*2n@T^>sHn=X9jHsAa#vfNix{_MNt%av;Rn1=0OCT4K0B zJ3twXc?Q%XynfV20_8*rB=HUbQCW6-wgJMY{5+#K*7&R@{i~IXtC=gthOo;b$+#guKh~;mKRl=u>FXQ8pdoInvSGLL<*hO_z@ND<8v;#4phm|GP<%_z!#UdO zVlgks_fFnEC#=%3v?;2_PlrjZfh1x0&B>fNhEdmCiq)C?3avk}7{qR4Kl%@x)0Bl6 zn>y-Jm3KU~$3|a~fT{5dVZu{oI$Xu;C?zZClw~{4MoDvX8plE@#uOh-+jgs>ORmDq ztOlAxu8rbwE*L4|ORMibQDW&`DJWL|Ksch=D5x4*gKW|)#+`3^!>QU$jBxzceT&6V zzUfWsEkhJv;S?W}Kk=A!$?R9Ud}b~P5o~dt@4b=|N7PudsTtJHE4|a}6*5&b0t!WQ ziLCdo6QYdH6V`P8HO@%J$ns=LBjMScU$@P|PWPc=Lvs+?wG+z_8cdN`GFv_$5ib&` zVQLamO=tNJA@6m1st3;2(2bHqQH~VRhh4uRoB!}NJzL)X98;gcbwQvK917KEwgk8V z)I_6bIU}RaBy*k-u%vUYm79UBzy*v%>xsN<+s-;qP+L&M#llF>ug=9Ra%FXS4G@gC*zwRa)0ief3LBUi|cr_eF-Hduf1gtx2kaezS!s1ts@zeln8$ zY3uC=f<`lzKZ3={U2?D#r~GgOD4GbWQSH4Nd1n$(t);)&v828JV$ynGoF`n(sY0A5 zDQ}imQu>-ktbt)Y4xUIl+P;_rtstY*4N2OCBJI;6ZT>1@HB*dg7gVZkjaLVJ ze2tMf`VM7qI}J|7(DBH88@c_G9hJ(6lHc{(-={&)5MaAEV{8q?>l`k0arNOe@AWRW z(AiatjqoZ}T{Q~S`xPtiz#)|KxO(!zK`FX((1zGR^dE2t5HigWseZiHWmpILaU0AB z*_eK_m?25Hohznv1@c;nD~s&;Dp3xt!dB_13Y|_3DZLI(ng6!WBSrqk&Gx;qOs(+V z`%#(PQA4C$)N>@kn~3_*UO|*$II=BG7J z84Pg5Fth8G>?g=fd&$>ikBi(F&}$mS$TFB5n(OEI4d8qe@vBvs+q8(YndCRjVE4ox zaH`hhk%mcoK6%JV?>rBLBO(l&Wm2Z84Zc<_D9Ni-!8lL>M_<1KEv(l=tXO8Oxmm7&t4AG8^R`+oQgS90qB8`gv91_W`6EZsXChBs5^XS?PYq9LM&^$S!$Lmeu_+g_+ozs#Dd3rCTAnb`7tz0QDS9h}0uBAwv2fu-)X@D3IZFM9z*|x9`d%jN)eCYJ2Efc& zKOs7VErJ*xdzAht?HF|sYddf{Y>2tgEL9ryV={eOnFwLI0=*eI5)|6UsXVxhx?R_4 ze2K(8!zp@+3{8(>75e1fA8f<@-P}1wwQPtHN%YAd$T0Kqt5~yjf;e?EgJz;YK7#on zbh*-pqS;E=vb8f>+P*aW71|klOJAm>oLuJPmv8Jj8D9CkH(lAR(bA3|nHY*E-;-{R z=XGvGlC({nFQLDiQ?uxm`bicdf%C>sM9Z(xEPPCBS?^2hKttxb)JEXQ5M05*nPPXJ zyAS`5>7A=3%Xf0e??oRPHb0XZhmIn^6Yn>>pq`%QI>*YpeyFwOo{m45NZF% zotV=i7%sz`K_}#LVD&vn`K_O6Q?VQQ=5deMbufW9o32KV6H?gfw)#hH;o8P{c1kRb&`%o>!5KorWh z#}SPEMa@;Er@1fRxd7UzB&%|lw)kXvPrCn3u}f+)h)xK`6lppL+r@1uiMFK8A~m8@ zc!GnZoZ-amLV77>ZRT5&l|XO`3bnIa-RP?^#P4pan0XMhO4R;Q0%_U6EK*LL&+E{B zh1i30zhJ@rC*4*J$;_pt&NvgU-vCj(3cw4&f6N+f*!8?Zz?Xpxwo<0f47PGVvh_w8 zs#;gRa9a$d!8_X2L;`82?`{cp;SR)$VPRwgY z-YRI(E4R(h1b~yGHVd%{q-Jnx*VXuJLS$ToweKAk?}aS6z*teSrE{`qCpZe{r-Nn1 zY&G6}6~`4cg-x_gTR;yXtS&md>-~WCV~u%4>PxCNsZ!yF z3d;{^bZqI(K$-;gwXQdac^G*->U#?dU(H<7`m+x6DUL<#v{lShC<5k(1}D|xAF7nY zptvb(qcZHgqNb0g^-QS~s(#r~QKAhhXw}IXb(Q3HC(Kh9CM4G~A?$4#j;|BqW>S)s+kbP$+&^Q2NK!LpZz`Pd{{i z`~f*zvN%Um`0pJ_8;*VkPG!d`a<0+7KeO6u4$UTG0o+BjW_2XVTnqa}+Ex1ato4hH zv-tCJ2zp|4Tc*?cy4lsaKU;P#U`LRerm-J!wO4asqXRYuOhxGEI2dF4%Y^B49ygPc z2XUZ!+EWzUVCKKn7ATWp zuRaJztd0^(87y}AWiHyneGPhOu^vn|*5RgykNf_kmOXqGl=Nr&9TU=0+pO8?j1U~@ zsrOZu6K_&Xx!!|kqUsQ&Y*j0#=VoXt0o+bkwI3mUM^EVX1+#^$Vgr8B<>Qc2a(}yt zP0=O;^Nr! z4WEk&yIGM=2_{DyJXx?45=inim3ZwWRi;RPN4vtTs93Kxq)^f!=lT`;Rd@U$j0)i= zC69vCSS$>BU?qyvbrob-Az{#4H&~8bjq!v`^-_L*p_bubvdo?s>zKD3ZEYH4!!ora z9(DRfBSaE1V3^H^(pDa$Nput$fD;n;KvP|GTr3v|;$Pt~WArTN4jOe@$WHz|fpA(3 zuswTD&bJ`9=0_`4H_BSB z3>zVU5CKmPiNyNA4}&plkp7E)tIJ(9!4AFJcbbvA=mv=~e3^Y8j8i$B)9xJV@LdgY zzh}Y*33ny+@iQ2aPaRWGmFkt$#s=Ypp%*(WwN5O?9?Y^t)jX%#LjE2O6qEnzIMGl=JcDpNE% z8MJ;EUEF&J7X<8Oz+Y~4+^NKDgh}V3AVSx+S8yh>pX$O9ae{DW4yHQtGd7=}P}?oP zt@38c94fW6p8_MJnDzRm^zHrph!&xFYVENNleVU~o$_>`)fCv=XvmNJy1^wiEjAY-V%Js5)%yVm8~~0ar?b*9D=k=f`UVvwb1+WcKaKW zL}(LLZjcJ~@7qnprjc|c^LY;R5odFlcfIF09$YQ~a>FYJ)tROn5c;YGt?OV*xTrJq z4mFYo6GMsThO^Y$Q!N9VDMWMdrHZoTn^QqrCFRgH&9{A*8U2zU8R=YVhIZ&!GErnd z;At;5t`>ZrHM?>DRmq^!yXUUc8Q`Wg>Z~wtlp=|(%Nk_yaB0(bLsVKZATzv4uE!hT z=?k@gHi>+gSqv87-R{2I_Q*J*mBXD!z>G48hTt+!9WsonEHY9q0iU*QB^^jE-;*?_ z#AEtVP|-+qm(FGOm?;fYD<^26s6nNByrLVdY$T~9RECw1NROilW zvX!mSH+q=P+;5=A1D9v9npCe>Bvr&dp4$$A6*BW0sir7W{d9PO6qeJecTg4$x4F3mh@S4bT;_Sv{VxWd z8~n3YFJ*0^BdceNrvl=FPXLcbPS4R6PYsWT*TU4oTGmoaM-T6LNuSq-4v+Dr8Bh|` z(_bxu*7!kOfhgc;1nKY?eiH}+@R`S^&^9gGl}(@Zs-x!!IQTIXMLk&a0JO zKLGHY@tj0{5Ny&VhCoeDQ-3Fv)>cZI2-E8|0f9IYA1bXTJ!()kK`@lG)Mx$?Jnqjy z-^jAFPset6qc7U^A84LZ16m_ z0A96!A^L(G00qXkDY&6@AV-3Gw6&@D1zEIX4AxR#*%}+Hk_LCG;r{I@9T&8r^r~e% zSf6Vbv4#!AJ28!Do%Fw`Q z)xkYuA9{*#pg-F(fvLH9SV{MR?@&X2z9k2yrJR&~6rs%m2M>h6jsy?>jAjVVq4{Rm z3#}4zv;*?m6YL!vBP2;6l&~i};2UElJgT?06f}Z2wiIIWXLv&xgU?LNh}1qNYv>K| zy&XhrURcM-l->@SP~LcyeEx-zZ*pH1@is-$b$goP8T7-S2JmFPCWGGiygSZl$RzhB zj+a3SavI{{RhkbqFHC=gn4u-SY}Ozg zaaEz1It$jQuK}*`AAOvTMIB+mUn52F#UgR|dNC7l3SlCLSw`UqA;|>M6Z{gQ0R%4b zI`%`Hg8OkxOWVeN9EAJf564F*ON%Q8W>0m|h1nYU(5U2cglC_biOBuD&(ofyG7azWDAn- zp6cH3Cf|TT_RHn9Ci07!6uuK86-p376t?Kst%jZt{~#bictltqSrM5Ly&59Lf6l+e zuSnoT@RdM~AcvrW?<9LK`!YL)--hoK|5)$}lG?kj5TihrVE*7=?@G|vNEz|#h$PUM zkgt#=k!g@D(Ax1i1;5GAlZ~RH=XqL(TKfUS`3sBX1Sw*gQWp4HMbfgg43p^{l-bJ? zFA2>O>&UpHXro?7_eU4THQ;@V;Ui)oS|&yz79}1N3Kw;cm5HoQ&=&m_XPkf?ryL(2 zQx~HziXrSE6d~*pn>EBdMAUzfup9405E4BTbrCZX%R%5GZ_gc0VnEy%`ze}@@Ji@o z{P(Dc2|GE06fZfThb2|D#Eo+?Qz^`K(i9Ml52EL-VaC^c_$ zNy%J=37DOb!P7zW!QH{PgI$B4D3U3>C~hclC?w@zi{H!`&cMyM&+s!4Ggy2zGd44h zm_FHibD?;_yXUhvgq@824cmpjiQy-G9>X$&4gDfLu1VBa)2}kM*k2D#bZW5{U>5?u z9_@9o=Cf9@er0vA)lvg4GcR{F5t@vfP|jM;(w8TV6YkFM>+O8q72g%zbJ^YAci3+S zNC961=PYOqk9)?aX(uWNEGGlCx3pp{rcKTZs>11}8fLE_l~0uuzB-w>%(gL3FleQG zpmWj`VYpyi)>*K)ogZEbHJLSMv?$mAsHJYe`7M*q6^l4@AKefhCWJlY3>}wpgmQ{< zOz}}G&mc~7#+adA!Uo4hZK}!01Z`hvAKE1S?J`N{&%(E3U*;Jn>n1Lf(u#40%ID2z z)fYKe9%COq!SKSA^A>bf=j3hL_lZgb#+1lLO;rMWSjHQBZ;C>KMMFhXhO$eFOv?9( znQBuC7TxP%9iSZYT|(}%0~-76Bfur2x1q2 zBbvv+Gf3N#+sND6ueI)7AB-Q;@7~{7oZ&8gUar*R-S>9@M*zq26Sx*Q8z>pY85I^KDt#zzCmkXt_!Ix9Xgp4dH^W$+enO2wPFc=S z&bD@`4np^E-$L(~Ug~a^-kI*!aEFkJ2n~s2VdkjHWX#mLC|}WFA=bVuOJrt96|{PN zGaTVracZ%jd5ZZT#FVz2ty5&Prp1dmjOZUFoypWbJv5&ey-5jO2)#<;PBN(Ju3=W< zooAhQsWGrowGQ9TwoYn%Sd6XzwZQmw+1;fJ!BB8I{E>a8#k(abt(;Brkl;XM&W>%B zww?Xy*Wuc7D-;|Q8+?Ad%|m&aRBogSb5))?UDj+r*u}=!i%}tkxj2-sYXzRR$#n%@}zG=bBq)iMP76+{xuyyblwF zIK@695<(vChP;#fgcX7iYM`qAz5~9QhYi5lYcX?fwwW_+*bl!+ zOK(PZa1ophI}ck3S8v<4Jz8+Px~uPB;%>_PREc=*Xk$3)x};0v4!qRs-wwioyUN~?rw9g z!>1wsBfXW3`;KecYr5;r1CgJk2L;7r3mGo$j_yt^$+b-s(-1sVc>nI-Ih1dk=Qzf@ zTL>PF&pYt1-MpT3G}iVx5H#N6LGW9eLU?+rg0+}KN~uA53eZ2fP_V|n%ZmE;a%c>cR@k;*8uuC5d9rL(g^eN@@U!U>EgY_iZXcr zj2)kY(LX`11RmXg5IYK5YdQTRcmx8O8UFj=F;4+TRaRkcAFFhr(Qb9tjfdLp}!zXZ*TRXXI$UT+!Av_@tRJc6WPV;=&CV4PBo=}ARxrKOJp?%^ zH8plVo^TtVP+Q-l`U)Q=dE+6^A%p#a$AL6dCj{p(D^wYuRL2y*%UUA{DNXau={ zw_CGkaCt=3kfjMRvG3kEq76&qs>5sRB2mY_*3;(vq}uyQ%kS*;)REFb!Qo3PC6{dK z9paamkVmfe7F8A_rkfhieD%l}OehP5%66U(c>F4wHC|VBLjp~UjtBmYSj-OY*V3lB zezM45f)MEaHqk7eY2ZxygPqafFko$;q6H%zk$D=Vi^#~)Dq+`lQt!#E%h1-u22v9h zJ2K&)yfxmXBFuqXrU>;D+vr1H2T^i)rEUhk`-b|Omj~Pj$G5&AGKD_$6MQhTFw30q z5*Zp)jJ-E?u`?qL0HG2`lTFKiK>VPmXT&ER<5QhfB zNg?#tfL*zMAidjyvkrrg?o{L)^lfacwB{S-ftcfNj9wzf#hxd0IFvsa#+}`P!}b|z z7qG^}$a2TT08b8XLXFn0q(!{_h8NeRh`u(VyyPW(NG73GHVJK=Iz7o$N{svEHEL7r zz7SVj9hA=qvK*XFt{L1aEU&k?nD0+PK9t8-Dp?>#L?T0YIF<0p{Ejg)F0>>HCzKxrr8OTH#}+ z6;J9thCQ@B-o3Zo-d8l!62>%{F4K3%OE-pN1s@_)k$qGLV2Ky6G0PK$t0N(!2IND3 za?|&V!J;|A1=z6%T7dIOBD4_N{DOFcnHEy@2H)X5hXjD9hy(so{*sd}y&^L54gN)p z>5Yr)BMqtU^i6+kDIiM3U?$-$Y~J8~R3i#?m=l)IVA?#){KJ9T!NUQ@K|nAW*=b)} z&tur~zzR5_(d90C*BwVRp;4wt1rR~mcjIv&MrA(xWYmKfY z(SW2{=v)=ZZ}}RLzz{Pg&Jw_`izC~H>j*NSEC@YRALHUt2KQhT`OW&1%r6D#kXyNTO1*&KI0*dJrA(JowXr7ei1D0 zwn8DaLrv#?%p^jS>*8k5GWCAMwwf$Ui$H#1vbQ4;`+Sk2SlxARsJP<_vCDY&C-iG^ ze(}65M8e?_oS?JCe(_a1k=o}0^E=upKg^4rOVN0ho3{lgQO6xHE9=?O8 z%8(LVvm^Tvo)rgt?6OmYpVdMg<1r z=t1&JwMBjnOauDoX4)sIi+zo24pgj+Y+E zAZVYDiSBbtfw7c$k%XKEMFMMBGJl$2S?;XVevx3|cBb)_l*XhXJ>;4n#xZeNym*SF zio~J_+h;cZW)T;@_qkKMy|$!HTpaPEIT(|lc2jolc1d@??{J)df5YGp)s=?SD`X&q z`6Cp&SG^aum!VguH!?UrxGcCrq%`<*9-m&`Aa&fAxFHG-3f|&{;>+U4V&UTXVo^D! zV#eZh+183%9&zyzkr6>Eb@9?VMYEg*?yqOY%f=SdcGEaBJ~IY|Lo*76M_;(|+yx)} z{QdpY1R@2R1hRVJ!l1)|*oX}3K)a-JF>2Wk)$UR~M!E{R45MPh1!HS^1EYB3S;LRB zEv54ndD(geXr(<<%7yYJGud{cCQ>R2i{flw*d#$K$?Cu07g||uaoEzNaJ(e^MkUgK ztP!?WY9`B?YHf!GP0hAryO%o?=LZ)J&hySQ&Z|3WlkdmBPh{j~WNVaalz6DN4}mdN z0@tv&uuJKgbuFjX%CeI1`=ksThAStIlh;4xSP5_o@Z?bBKnkP@ScEC{X7#f4mV`xM zbJ609eGuOe|CwY8WHFfJlCVf!WqxcZwhHK7X2?rqO(sbkWm+~`&|)>zH%B)oVOZ3) z?=4+j>R|6@Uvy2ofj#4dR|^4yuj*k9i9wfCyro3=l<+mmE}lWt$QVnDynac`X~4RF zaD`&Ncxlgb2GT^{#A07;-)`1y7LpO2QPynG^m2abt9;c(9ooEkrCGh3o`FSzasBjR zp;|?26vy|rY4?l=H<-5pp92~Z>=2lGr+Qr?#<1G$?$HE;a{_Yut7cECQF;cZgb!+G zc8h^C^LQ*+NlVkmIoCNBebV4p>s`!Q z&7;hJn3tJ*G_WTzXrGIX-I|DA2#z1s4$ zd?4b!?B3~-?Y)Y~D9O zMyW@^6O0KI2@(sKh2OF)&?%!Yv&I1!toIg*u5?Uwi0A}$q$!!~%?^B+?;0AdR{N$l zHaAQ*J;W_V$wUn$r^Qp#8kkP>CS!Bsgs-A;qJIpSktdS_Ww2$`^Zawmg?}Y8YAtvh zVN8ilb{mcCo}b5_-=2Tl&6@fyd6cwHmt);Kl5`0SF;Gv`1AKCrJ(*9gajHQykOIm8 zFZEkCdrtQpeQX+RKympR%o)>h2Aub+{a0>09_!fan_^GH3`wm@k4h#rw|rjN*wXFH ziY-d_>}&)sE|(>#bE(TLxYn{Zh+A~0fKz26*60rd^R8$28}}tLq(*tQCgn#<7d9}u z($%*RrJDNOHd!C&sNu+pshl}z^j6lje`yO_2?ps5R7dyaY2|5*k^Vf} zu8&iSry}21?@+4L!LPF%?X2UU4WU54phVDUF$mg=P9^4ws~P$p`*fqUXHbTwj~4Rj zqua;Fn>G>sywcNInfAQJcD%NC9#%&4-FhkUtrf^}4~iq@m0XohsLM9}oLDF_Ntvyhl`p%qc{sShFeaHXUCnIbZp$*! zIq&@`&Y1GW%(-5vwz0-upgaCLzlhxDT3TCan8TWKy3juzS*+Jz+H0a}Sau=38a->~cL+G7@(*#F zfh>bzKorNFeK5IiN3eCW$2mgSJ-lW)*>;Jzj!1u$IUZ_G57uCCFLN?IX~_ECdZnnL zxEe<~m2u7<$BDC7aW8&XPc`---R;U{{0Ikf3feh+DBa^)=LUWgqxmWI++b02X{{^4 zi6%f@zLn9vbvx`T<+8rW0vB<_MbM-Eq~gTmYg>e~#}n^L0OAugEaJ8HzfR$v0fc{< z!u`e%{xOAn1_l1i;Qon|*!*Y6$1`SO2LdBNQ$0~5T^kUh@f%!F`y28>_h;((jA{IC zlUK`D%hbZ)Hwa_%7y9xyu;T9r8-CY#fmNOX2%Bfvgzh&MLZhgpjK{!8&By{mM`#(T zSs3Z?%qWN{%8@T<=>#(KUMjngFof}PRsuaJpNlP1O777znL;UkcAou zVEVf$1Aaq#G$1VlUkvNN&@w$8$aX;%Ne^J627qjto{5 zZT_p{eX00|y?+P({@qi)7}g)U|2r}Ma-M%34*MU7@m%o_d;f(PFK_M--T#>wz`q>N z|5%KdihtPqFU0t}>-aqgz2NTuw4LWn5aQ2o4mxaSWDXk5gmpoC1&nN+sDxfdDjg8? zuVrrgTnjWZ0$;o%$e^CrPJg$={rto8iQC#p%T!zrG&owwn;X5H+ynmhYyUL~FlKS=P)hJ6$ zZmqv-WKI&>RjSj0Izk4*R7277mEB>uk!%<9g-O$B{a4Yw*wIZnqnz8S#X*npCeda& zwj|dUje1)av98j~JCUMg#3bwG*=FbKxv_$Ce3R8#&azG0FNp2x;ZouCdN*m*MhkLk z3}Jgyct6@|hrVA%Pb0|UBDT8X*5Bm}JL8_5wGk2H47?eMZ)%kMp7(2VL1J6(m&pyo z-Pox~G5y7p>m`Fhn*Y^$(?HB;S*AiwJg@u$U3@P_8Mw&LDkK6znP(p^UcrWjPXdW( z*`Q{0#5v>rcEUJ}NY zG$HvU@IzQ`f2OqhsFyDj_1K>Ws-BEFjCH%B1JSH9!Uh~Qyus37*C`F_W zL7S=wUq>}^CZ@OoKVIX~$kYgDA&3PPfgakzYpr}rCT05Ljtm|nSvj1$?Ry1=;*&=f z)doH>FKWj?edeoV;-I6nCCC8|QKB5%NO{MUhMpj zrT&NC4X-fYv;9Ap0YLutKm8m)bh1u1wt8m5=K2;dhg@a!42(by*9nh|8?>uO4-{=l zKF4#Y1OByWc+Y>J_CQ_yZtYKJ&-^}DRl{SVhhw6DNk8)g(ia0L!hSZ8XKQ#-@V}bH z-(JDXE0NGL1MM^V_l^;CsM+9oPu2^`?*W>M5yTJ`5WoP6v_bOF(E?dOzEv1BuQ1Z# zHa9T+y&1~}%J71&3;jdt1Fxio{T@O zF|K|Z8(vKCeZ1qUZa#8bZE*sg>9?J=8t{RjN)Bl|c3V!$!8cs^QJB#%?vG1!oq-vAtYGQZ{4?v4E z0e--yqy8{}=z4EPS#|82T~D?Adq&PP;U1fY6`|K|l{y#Mez|-77{1q>Jr{%}=;o?9 z!wL>ar@`U(L7ys_=Ej?#s$zKF8 zLQFR-uu;NWNYzQMA*RG|Ndd8o13lOMLQm*nTg}2$PH!GAFp-Hqo;$ssYc@w|8HD*k?dmv#!@nZi~(AN*iH(>QdH z7^$FPMI`sGXn!BQ7*hy}^dQQO>LaankMMgXfsZ6GyN0atWHH;@1=|skczbh!GPndT z{hY4O{fGn2)8rY$TuEHQ1iAjH5m&n;?b)r`9;;ywBRfo3t@NSv8>7ajgr~q$Xz@v0 z_!7MWb`i!clT(W*Ff3u^@r8nuEx~gS-~E$DZdd-~Xs(2Y37V_Q6%sCDk8EjWQG>}+ zS}{c`R|QqUgF-7J&X57?^D7^+2e;&`yH^FrP1nBXZo8lI8lDVS!jObxEMkr#ab+=9 z=()d7rR*%8({%D_c^#Xrk8RZF8sx3cp>LGY5SH|(ZQX5o6h?&SU`&Ya0-BP{^RC7N zc93qNIjF_n{e1n&Z>&deL-{0NvhjL2epZxesD8kKtnY6768G@Q?&)bu@P`E<1~{%c z-~JKdYEKFdUPX+<@r~zw(%>}r@->kmmRZ*K4I7Tu7*2_dtg%V+=~L(j;KQc}mXb)cdyIuFI~kY70?f05=S)giX;Da^zKdaIO!~o{0GK zzUQ&uI>oxS-xuD8-n-rC-Q;f6F6)wBes6Qnu4p=XaGz{1dwP82cv!ySiXsTrLT!~2 zNgluTyDsf;Tty6B*VY7 zYlD;8YX(jy;TVxTYjP^R+@eW{U5ef2$-c?HQ`eh>(}3XoQWBJFfp|BtVQ2_ z!+I%v3%OC`(QIJ9FnL&;JHMkGGgT_FM8VXKX*Ca~5qjY(A}P{Iq6gp9NcHaK2RLF2 zxzuy#b(_miZ#ndlnfc;V1^dt>Mj3aKCV3tl%p|eJ2iU zq2Y{}ZKuBfoJyS*OOroD3t3KBANBR-{Q!wm`T2WiC?uo!+}xZ@^OPQG8LUwz&*EVi z@x_37w|BOh-pn}UTAHJN9UpsD&kw7-%&P{>tDB0P`$q{{>~gU@%X z_tc}S{^+umb&s|+c;Bkayz_1LUO)Ew%}wNzczRa-rH1HiB=T_T)vn(u>bjkih%<~CHc7_X8D!`%%PIaO`9t_H$;i#Gn^lPBlFwSW1PL+l{d6rq3<}rT zs}iQK#@^X)7X+Fdx8X*HoJwws774`vVCxq z25(jw5`BO}hq~viIJ9a`MG*hGh^mAPC1OlMT2M=By?dD&E0ON7VrieI8tCiCi@ab1 zOC-($l%)Qat`ewXutgtfv+J=%Kb+e8S)!OUS!n7wxqC~Oz(H3AFu}X6Mvrlv786|- zv;>PFxqXa)&Rg7vBm#j_5&D> zr*+m=@-n?G!Vk}P$?Wd+pAV>8w5gL*=~*y@H>6Ps^GRq_Q&a3FmIh+9_4LZROss7; zvbTP)M6)w(4;)kK5>${Bhz-v>G6el#X?fd7Sk;_gZ{rdnL2WU{j6sS)lgh^8_IQ72jM0i$^{m=fM zl)+2BoL;YcY2>ujom3!`QoKnxu?mJn@DW1fXb6)Xe<5k)*3LXiExDaP;I8|D3o#DP&=M4m=fe!oXH>30CkN ziMH58ug&$v&N_p_)$4&RYz{A!rifvlRcccljgUi{PdbpUf)U)*ef;J_{5B)|uNv9w z0x-WO&8Ciz9-b>Z1S`*_QO%}#^)1G9Y?*iRZDgl%a2#Lka6fJ7ZQ9UZdo;Fow{V%+ zTQBgR{UjlAsKcnVUq}U(58svYPFOT3cv?RO+4LlO(nG`2MXf@~jAk%JLSfjXv5@;! zbVfN_MT{VnZjsZ|5f{{F7<^RuT3y|ldgH+{9m6${zESY453VBWeFi!Gw5k1JDX@IZ zFug`Ll@W$WFp<$f-Jrm}mm@QIe((&QRFgdkSWihuC#Xydl)b*(a_OtmFrhTa9I`Y( z&_|Lq4@09K>&N`WJ{`>EpVnoP@2PKZFCh#%_E6_QeK26r$euJFlLq6P;FrMmT@3mw z5UGIF<2YQ=eDLzi`5b`N-SI8ihoqVLMn@(j*}|E?U-=&}*-pY|mxjem01i$hO7W@9 zuX3tg*sbD6j51ye;cXvEOqyZ*V%({lKl}O7b0K<;(cti-7aYpsnAx(Lmx~P|Q3r)% zafgf&!BDg&hAuY$_XDH@c8>#3?tXN4g$>wyXK6%Y_K{&0yrK(k)75;Sl?K(_qQX;D3DYPK8}!eaB*tdwqrd5Bri5Ph7G z(g;x|QNSLZSZ#Y;AA2zjZ~`rKn5AN$q1TOY96g-tV+(IxT}wq)87YoOxftm!m2tB{ z#f-L+skgFJZu5F@TX2gx;ek#hL;y81X6%E2C$mKZ5%4>?twhS(H@YZN?=iYQ7=AS; zo%3Ny9;Om9r_`}X7$RYc!>3CU^;FIYUDh>j`C0??l0Us!f|^Ew5^~xL1(hnGR?1N5aNeH@-@qOMjIlre3L%2#y{M z{INX>A02~12y^GP8{VrKM~q%ytt!lDgt!W2S7l-zbj(7j@Asj&9f|I@7#&GJ)TW)Okku= zWxsYXqN%wpix%QNa!Ts_MM)%FfP}={%h?D@X~w8KQAuLaLOjNLnbA`QT$wqeNYDfd z5`}_TFXXn&O0>5o+Cf9;MZlL>A}8aVSh_H0Bo{KeF$?wMI}5}gr-hxf@ho9z6U*5^ z&z3G+l4LX)X_#NU_gNbyw2sW4n!De3ds3EwrD3y$nc2=uMaw|* zXIT%~QaOB4z}ies2#v!{_VV=Nd7Lj`pjhY0sq@jj22d}?p2jQ!EjEXTKYu=ZG*(DK zI(rN$=ueXYVCL;k(QUKl4$_f1^K-sw7hf7f#||gvUk6U?_c9?Ab}R9H*tX6i#Qr=X z>d#@vG^J7ZP&g!-U*)F(Efs6LL$MklXeUvJLmQ`v|%7TZG($ z^m{%f;CFD_*!G1f@p2xpO$P+8@ z`;w5S;`kjV==&7I-Q4EC2eqv-DSxeiPOi4+&HhQFPWUGI4S3@ zm<58^jMHj*1C?mR-9zu)eFtmBskXIE(@&k4p`TuxX_~H|AkFM$8@-Lx>uVfYN7uH+ zx>cu0Nsm~+r-Mo)+?=UzF0@f0qGC&q@%hFs6!&iLKQV2L=YV+AKW~@5d|~^2{l5OM zdj9YN{e=GP5%6E&j-#wiIW=LS(oB;_Ao$p762reDXIl%79hJF z4xU-D0M!CC3$WP>kJ~N4W-mNtw*Z^n<>KYoEkL#a#R60d&`iJ>oAu3gE^|KYeAf93 zr+lG!q3c_c=%a$2I2F+*YYx#yoKBbPRZ$9vqJ#kwoG!^G9uf}03MtT?hDIa_S;(uJ z)FUk2FUy)yT-u=Z>ChdywCxbpAs@in@7@^2ZA`bM#Qc%vv47dNpYTH8n)@)!Z9;b?Sjnbqht z(v8mYIL^ZxS%yq2<)!QsMr-TZHd?^OaY+`$y9SPl@}!zTm0;#{@~ZuWu#hMoa!J&n7ylA@xxk4-LzN0Lh7 zE;l8%a8Le|ZjVQ3;Q-4SuFYhQ)E=oN%_o{q;5GUV>xVNV>taR&D_v>v@Z+@TrsB@x z`~6S*f8u=G`G=TY^@rWnQPHmz`TYJ@-JXEkA8>nIJ;IZQpH*$Rce(|)+hY{bLNoSz zB>JX-SMH%2&W|wi*v@Tx!r1=T<18L zKT5>>Q92HPt)Mc%853J#h7%mo<#)S*A4OV^Si85QsK^tS0Cji7T^=8&L>^9wnlq@1 z{eUm1qn6R7vB6;ds4=4((#dkgR_QM*Dig=lk0M0@o02R~xBRf^p^lrnU)X-h_Nr&^ z65i?CKl7G*dZ~TGou_`@N4MymcN~8F;XN~(gTmjR@4tF}|LI@agH1eFd_^)R4;KdbRj{f6Gz|zj=&h841OGEfGfKd zhJI(3bP6RYEhvf60uFp;L>vx4UZS%UqC1`J$kGOi94ynAAt_shZH{f8U$|w}<_HUu z{TCKp5SbX8t4{rWG$?+R}#Z#(|r zi4MHn8Tb_%f?uH__!U~Oa{N*$QFT>NCul%Q3lUYg%s3+M&#F60z1IBnS`^!B64;pf4Rq2f4(kdXUgyIlc}a6rN+fIS_YP-U7k48?RGFe(`I73l(Ssxs}@ z8OnKepqx!W{Oq&WZ@!>ScoUrW?B^GB_2=L1@9+P~ z3lHw6-|2tg^OJBKt@v#DZJ64RgGVxdqJx~^oW4krOYC-=jfheSlz&YvaS*$WbxuI{ zHQ45e7bLWVOVFY&Nfj)1Y4xY7Iawze?yQ=i36!2-+yz~Gnj!-P<9{ zKnFnG9mGyCjhP%FFge1Uz39+-FJ@`s_qbH?^>@Y0*S{5Skq^Gm-~4>P>jfrTRpkF6 zRLc*M5WSN1&4dKRhyDSI=NJOphrqTWurdVJ^1WTjbOYvBuTcSDON0{YbZJy1L0!#w zG$j}p9Ui@$lv9__k^y{H-})enu)ZzN`ch7sx7jDFliO`;Y+GzQY!b00Y&&f^Td(a6 z8-&gLXPm7AU~^h%0q~D*x0?fBaHfrvFZc)m_!5~Fmggk+4k&;PauB~h&gp>dps<33 z>6qP13s`RsN88#{$Mh3Td_?qPrx-D2W8rP|HG1po^^N7sADa=sj3Bv#O)7dBnqQFF^J zU3>TXGgT#zJ*uCxcM?=%esV5s_hafc}Dw+yP9w-maohg9xF; z0F_r-QqpL6+YY)Uo>UZyG=%J4r#B!1b3Ad`7I0`z5TF7h15aTE&k6*6$efPYo|8&l1CXHOlfK^)wz}6gI$y0M}nY#A38p zE0E&T``B2hGlC3jLJeaYa>0{9VNGynFc-`RrJxY75G7!dtbm0vffRZhkLhUMNpuQH zkT=LtC=uq6o0HB6vGx((Dn{%W%;`Ha89~+x2$O7H3Vcq^D4IHJC>nt76;J>W7xoHc zK1dOpfEZpT-812S;wHnbxNRx7;*3$3-4mlE1HtV^0$NHTtT{BIiL$3htn2QOZD=zj zwM=6(ll~dM-r9Xi%!cQmrA5_ND(O(f}F;MN=dv(Xc61wHnqjEL|iGaR+l*JMG#~;#(M!67qj;* zj?;ZKe!0D(o(l0SBL{`&n7qg5CS@bzW}~iCO7HW>B}WMOc5;Bc{8xt8l?@q? zkN?Q$FaT3n2k;3eo&b6cwX(V)=c2A?DXzLw=>~S*UjiAdl$Hh$<-ayr;=XLKUbhq# z-NG5&L?^?a)hld9MRh}|t=TryCfY3QvO%C=WGIuhB^b8Y!VI5n=qDUe3J6D>laCST-T$BrQnM^^PYwBCu#MD7h6Glh~BPDe0x|mT+ z5QMS@VIDh(Tvq^kf!EOOB^^|z)^K&`M6c5Yzo=`HpL0~np~W~y#UOAd;A>oPSsSFd z`r5X&KrI?lKyZjxCy?5_0b3cD=V)2F66{qGFFRQC*@3_Ie@+8`{4D|_zCNz)xpmR@ zzITO-oa3^$Z+@0$Lr-+mQYs<}p{oD={(tL>wA*T3NShDra0;T{NJYgF}>~7x?|Aj znomHTvot!Q|6;5Ay2G$8w5@}AVL#ERqKb;6M;x=KL*ZRNYLqpXi^gCwhit5aZJ5Zn zK7P*f=1VU-=iGD8x-3v4r5{^+-uS00r#82*>w}5Kr05@^4}Z4z;%UDkF%DMla5Zew>4W+k;iKVvSW4If?m*B7icXcFOLMuM?lZ``dk9%~50LeE z)?E?iR6NWnx`Wel2dCwZ0b1_hRMSz;dJ?IpOj_>Xv>ey}Hfgzo({fy&HlQIYEe_5u z95nBsjv3I0dW|Tf>*14OVNG~vI2Y~>OJPx{FA7?uJ!s8Ni1em`BFKmPbhp=QQgLgD zzdVDSYyUycy+g>k#K)J_?)y*nFe3y3i%(28IU#Uy9)GGyN8%YbaF5O0CY_n9I4W2) zKB#zA&92!rQPI<0#T}y_%~zoH)i*Nn82lk-s4guC91&$GSao|q@Y(DHh7`zs5hwSE zE8qm5VG0QT<5#`c{@5&C>#kmT-ukDd^aHylubDpTy1wFTSMecE3Bx zd7(-S4@-sHC7u z4lhglV(F-w9!ArI42$}tQ6tj`-xG*y_@>zHF(H-;x`vljrf5nI%51g(nUxl~WVl*V z5)`@SlmW$W!>@2^0|y8-)}AmwF=KeD!tHX%W%1&Y7{W^sU82xbMLE)-w8Vx-QKHat zca%m?AQq4_SWGwp3^qY$!A-x0?w|_op*h1jyd>fGpWDJJMpKR&E_TJ5f;YH8Jwx$S zhY$$WLY945HBE-mVT|!InhF53pH((2I;zZ?rDfK|ImQmBSWkKJ11tun0}B?44UX5OewS5!hQ zZ(yW57zFX=b21PLN}(X4e;5<3NYCHzTJZDhRzEj;*8H>jJ1$y&#r6OA))W7}O+M&( z;o00{jpOJ$bGKY`+gIQDS^qyDqQBFxyz`QC*H4;!MKZK7GxmuktAD(B`D-`2@3`y6 zOJ~;CudF(2@6}hmzJ9}B84rMHFCIrctR^gCv-(A1x5lPBE1<6@z~lr;o#a0;MI69( z3q}q|Bm+4H4Oezc=0Xlk_aVkPBP832W>=`95m~j%RFEr}HYL|)-aDedcLZt<XCZrf(>RKG(WRPWKACy#4CB>Qc zZ6+}^RW^LV1{n;ip)j3QybpVIf;OEpJd9gt%$NkB0DZyIeC&et3QrYe+35iG)xMWO z-{5y7b0kA*o12lwY?EU(o86vLwScN>L=*%tumGiK2n2rBZilx*v1zJEs9fu$&T_k9 z80r?-b9!iOuOV-d1sRZ`CIo|)J3jdh6G~1*BYkasZPDKjT!5&r}PK?9X~pj zDh+2oJCG? zFi>bgXW)}-5N0jq<~5|VmYO!-u~~Ht6Fg?BJJg+OPVH4cP*18hqL!*_Fr<$bl8&l* zRV#(r4uPkF2owFGd~czI>a2J@C5o&_nqo`IM0!-(DdnVI>8PaiN+%_OND1i;TuBi5 zOz{>#LH3cjP$r?Dg%&Bm6cXI~gbTR<`niG!;A;c+6x^F(|4)T>9jqA4B?a=zH4uhc zOb1yvl!986)I7)bOPZ`OZyK;CdW6d_{?YYCB2?CG-QC^N-(G+Ht0E~4e4%7k{oj|B|qYF?xarBV&<8$8s*EbmM)5{cxCJ8WU-lhj>gvor3* zbT)Qm48{iQEso3}oH_E=E1d1>hucQ6ADgFBRVOWtPxMa=&Gyd@wfoyc-xj_tKJ0o@ ze=_Q{yCT{OVY#?MzRJ1AwZ-*4=U#Q6w%6$lI&X9SMG)QP3p}emH+V!3ypqou={i{P zXOVU^YX{j0)5A%k5|76LRn2N+98KpYTMJP=7D+ceSx<$Vt0C_482asuY$X4gIeCzc zR5&uFpe4|w4R?m4Glfs302F2o1x_NxNMtF*bftwty2tT6h6FUqNEtmpUSvt!MHUM$ zGFiA$mMyAy-A0)-czpio(C3w?WG~#`!q7w?(O9h6XJ2xji1A6fP*Y4v5b%7dr~dE%!~p zKLHwMDYgyVdOX0egW%_4)9&KWfADVq|E&A#?JxYXbXVktdE1_Q@|G2M(OX0NU#G=X zd!7n6?s_b?vg0RjzWwq|7y;;`R=0e!3V=dZ)pFe;tJ{LRv!1~mJ}VVD>=ukcK@O{V z18rvVg4sMM!W>Xw_Zp~bkTf%;kdtXt1$SN{htEpoRSo2@4d!5eh(Y35fT6KNTP&2J zSt4YD_xZmn_;u6&EID}I` z-UK`1MsK$pvOjM08nq1a!W4yT%?q!)6Z$w+JmAYNCwfk3al$2vl@4AIOypz~msR3r z?xZ97+eh@bmb<~sspn$fO50-l6~0Z%HMR}*1Ij_qKHtBTuhc4s zw~AD`D&19{N?&bY92x8ThW$4CgW?0ur|C1oGY$|pvQIha{<-wF@{W33I_~+{cS`wE zjXTi2N{f>pb$)d7qlX`T1pz9ixjmAPc9A2KIkug{l()mxTrZj5uv zfqIdqw}%WEL!K^>z{@1>{xr{Qfts7Pe` zskXLoU)x%InYh;${%AnPW0D^G;MEJJLS~6Vmu#IrRP-xIfQ1vVa;sZEY;)^P?8jjC zo0cn`Z944c&Fyw2oC)DYxaTN-?))3wq|TFoj%K=!nai$qTCQOr{{ax2H+I|V;KxIH z+3ab#`itgSg|VAQciR%?O&@NS484ic_jwYmLdeu_?6K9cYW9$E0&`ZQdhV=dHS)6A zJ~TxMm!MJO9=`Un8l(4>Vx6p9M&muI{$A>i{$LZ*3cGYh{4> z_uqmitb0rl=5nw<#Dw9bSSiwJ{f7=d+bq>Td*IR0XYbq9-+k!WVZVc5{Ox1jSA{G4 z9{lAIVd+=z3Y+(S{W^@F8T z=l5}D_l@%bq}&(8FG$RG%#W{%uTwX=H+gQ=ws{_KJ?rW59Cv^0(cMmG!s`iO{&+o3 z)rS>t(V(VawTsIshgCHgibhI8KY|2nN%SF*)*?2uLm^UD&RnS^9LC(Rm%zF(=tL~B zxF~o)N&Ym5JecR+Y)RVvh+_Hn0hne207s1o>l1~O8l^2!v8G~6g;-G@wtTT+i?4+T ze6ivGr!UqtT@Y|WsCPa2|KZLA0AE<1*yLHy47K80o>;w&dBHQ0V__!W3`|V!kA{@zalgA2f1T=)JTZA79Aat2*@SY5Ylp75BPnV9$K4W!9MW(!gv8|g)JBg zhWtr!ga9*t(u-tnv`AHC*QMedd5(6evqM}WFVQ-jS|~2rym5yk0Bjo!%n3jy zu>|daWy>=3Y@i|rfmvvYabq_;!JinQ{7@6nx&f5|bF7;o2y+zkx8MMG7_$^6NFxPK zt%_7b9b9)d9ZqS{cF2;0m{9ydjK)|V2XjXOX7W83zA(Q`?Uu=sNHHeB0#BT77RUla z1CCsKA$TRg$OA;>eq7>YVmJ$S!O=j~rKA}{0F8&2%N{ELyj;X@jEpjuR3U!D#6i=J z4Tlu)xowDvz{F}qSpjR;wh@MrhB{lGtzIr)u1XdGjqP@Lxg_pzNWeiBxKWVAfo*<$ zER1Cc1!BfA;tw#<+YA(%^yKY7{R0hN`?otj=s$5_&(_=abltjj4;DpM-gR~VpZbpc z?Iv16U9bJ}wO{}Am#;$lz8QMsQApomx}V|H0TM+=s&Hxx34f50vXe%{7ih@%X@%V% zbW(rN0qczyF$JVPc!tj-I0Pkv7D^cLY%moLG3P{-dp<(k=MnNT4Kl=h9`H;9;_5Z7 z3*L?p_jcgA;JFSV_y|Og*_R;|C@uLA?G4e;j3^uSAais?Pez3`(Vfv;G#{0iPHB#m z(;{3LF*amf%(KoO;_hn}-jh(@P>-q-5>1_-0j5h!<&9FI^K&QqY15O%;!|+V9oOoN z$QgP*%%N;>|96)LcXq(DbpjRuGf+YP6e2z031hiHc&76Eg65}X>qg|gAWfi$eBqhS|-)s*4F7zT^Yn}2)R6Ek&3 zx5InoMHk(5R`(;_=dGGKdcAOOU)P-@r(QJs?rlONw>Ghuu!!7?HK*0WJcb9RxVhg^ ziU_-u7ycv`YlHYY#v>Vp0dUf&Vm%S|a!Z{TR!#`qVQ)>wQB~q`m%3-VMYlUZWgL+Rf3Nk-y8fdo)HGO;F?D?O!l047Gt@xKPN=kI09bJ8keN|x z5lEI!8;N5`J_lH}#pxYG@;T@@!+U1Hds7xEN?DkmGI75vn_3Ua1(cJ8 zpLq=k#XB-)jF}l=#=;&82wR{?17VM#OoceJLvV2&B}oW`WeJ6en~(_M0sxU}jqRmW za=5ENoA!2Kqz3{U17MLLR~Y1i$-g$khMAEq9@2P@FuI|V5iXqJ8V^F=VbN!4%EK>; zI5AuELU8lK+1Je)L&qFiweM?cd*$vE*Ix7Y$De;!_~la@Htu{(-oVEHcThQ9$S~}F5kbKN zIy;L}FctX$rwqJnNE~VoEAmi>A_=M@C@v^X9td4Ri>DUS3DZ!Sf*RZJ@lg+i3CL6L z8?z#f^E~%U_uC(GKkVt1dzD_>FFmTq2sR>CRxNTx_0e>^<3@Uy!(QvVL~6CQI_A0` zpbu&fI`#`a&YwG8b-$*+EB;pfwd)W1hnmmQ{~S)@^LoNAa3?0oA?gT_M?oA7(KJC} z9@8c-loe>o>=ZMWDhTGbt149#6)UF^3I?&$<)R*sOGn%k=$J!v>YCyaJevLrc|{fU z6j1|M>mdrRR}k=#a*6?`Q`A%yG5HFFXasy~Gkw%|zUz8tx#n4@sMi|^?2hd>lv&Ca zMO1o(iH18NUN4l-MAx71-ONo^ZKq7jIt=Ujhx(}#AGHlgc1+!3hV{0BWxcHsY1`=W zY_)Sm$~+#na}BG>-c(RZy4~U8M#$~&8;)YcZHExu9pcw}${Hb!_ZnJJBP}m$R3YD4 zJh_#-iP1l8TzNs1F7G}JDjQr*SD94DWjadhAqW%A#T6b>!SN+buU(pL%``Z}KHG{M6hP+lMl`x#^3Rx3$K$wmo zWx`l)im4hx28NQ3^pNz_5V&FpoE!p|4}r^uzzGXBHfN>sK>2v}0(DYFwtPwXX7w)h zmWuEBzhCpR=u$(`aHwut&D$Y4CbS5GK8kAL`S$thd~LpCzH`27g?)v(LR;Zj;auVB zPIp&&nDee;*qDlWTB~DmdU4f;hMzG=DL%qc3SV(W#PEk3 zMzP07F;WrX1cS-tfO!mcB*8??NP<1ZLIZH*!AN8LQ0!Rt$hoIFGJoLNXW3yRy|$-j*^%akY=?9VIl6+A>NQ#e&2t2yav& z78?c}HVi@w2UrUku%9(DVdAe5%qA*4+}`iuF9JgY@2&vmzF2KyFOz!q( zJ>?$|+KlZb1o)u~(OYe6r(3~&j00tKNX+YIaOsK9*0n5#%gTY26U>l}bMuL{OzlFj z$2#t(W+zNpd_nXWEk-<`Q8~OMDF7%5M> zoc3W_j8;{unv#)Xq*O0vQ6(8hm&^kma{d*9F_829H6pBb_cD z!2eI{2OOX-BeYYIq%vl!WYlKcx*CW?AI#lCv2WS@6k)1$DrH>?XncM6s!sme2iWh z*HYQ(sgOs@lV!QNG*>E=mX^os>aLU zo-2b_Mte)&alRXRH}Yryr=d?HeiocNmU##}#Zlgf+3%neg?!a7Q$L_4dt+2 zv;;8Gl)H>bR!j*(FGXC)PMV`9sZ@%E;4=}&#~g($-Uh(eMlsWX5UGv1LoxkvL`^J( zsT;__v=I(`ZR#BqA(6Nauv%%PWa^m#kbE*=*&BkY_=G`#2ZPN&(^15=4<0njeCJRs zFn!3{SU1&lcabT?IV6QeGAZ-5sJou&Pj(q-Mw!2 z<-67z{r~vkiz|hOmV2&#{;8|3ejcH-|GInT-LJ0i|GfY0@6h{SYPsXcFW-3O2)N_T zV3A{)f1BROx#Jn$?Sd=gzxuNO!)HB^bjo9DWb<{7aREZs;!J@&|@*k9@cS(=H%Nm(rAhUUC1!H=d1A_5T#C9$(n zR6n}mQ9^s+t3<~RMobSxQGRLSLjkq8o0)JoGqH0^gymyKX?c#g01D1#GDA8;2{q{= z;_H~dnHk2ob=Ja#!a3Fjjgk*>of!jtbGLl(%PGvNdT#$k;wO+>m@YVsQ-XGf1C-Q} z3S8)z98lEaNO6rL9jHk*I>rPpa7+ngZF3#V9A9exDsqoV)>NL8Jg4%)${jU3Yiwi6 z#tdt&nc|pIHhI{bvN^+++ZL5A8rEL3rRLqr<7J;EKdI8ki)7mKsheLOwPXxygZBr9uifNq`6Q)KAQ?OO}fp9zNr}j_hA^-Wr16xUl9E zR4D}53{!AsLmNCaMaoMpX|L2WMUS%sRkG;(g8;@H5%!a@sfqDl`nQ7zr&7&URjb=$&jdUfs(C$IeVoiASV zy(NFx`J+!B`rdV$pLyY$jnB-DUX&WOc;49D9kl7a2PxhD;FhmfeDV6m?~B#H?tSUC zpS!|Wo`Ogcs`da<1MazB5{mY9sDObBTd9MjvQ*^7V)4Scb-G6)FLG2mG zi~1q&!N4cl$AN#j`t*MX^6?TMR&%<~Z7Db#gMLRmCV3`#ZUKfy23~@<62g}0DMS#K z$K%xD=|cooB;fa_d|Ch(9w%IRDTf9srulscXmBX37f4)>3$^i=;zGPfXx{5Vj~Ia- zVUFQw_8C55f$t@s;On91?(@)cGC2mf-W;>fjD)k!Inyc5a^{@^2H@PTT10uEjP6)s zGu(RU!oIaw1P=WY;EAw)>R1Fz)7PGehV>I10z<=MB1_q{L&=2F8P2#1+9n)!O-*+A z?A-8&g$Mm%hpgD$`<)xXwboX~msHoeVZRo$^S-q+vw$f89RrNh|)s3647FK@s^Ihk*&StX& ztCo~B=y_6H&1T7jdx_sdSCFouhWr6EM!I)Mo??qjB0W9?jtzmMLtvKQBF8Mh1+)AX z%<^0C(DiSwA#N=95ZE&WX6?70W$m|M)_x0S?YCgR#S-}Y4lcg(4N3P7fn5bvlm#Sm zY7d+6?~UmQV@yfMq+{xzLLVmN-^!;GLdc#-s^M4ywvl8>Tq$A{8cPC_z(DPd6y1^9 znG#Z=P}H5;;iX=QaU3t#1Ywzj>6>vJZ-ARHnK38CD85%k6AfDk97+Rue9WsIF`{wF7<-UK2bcg_>f#CR~4Jcv(#`z9cKgBaV>TZ_2?j zZfeK%*PP?UP}M#n)F-VIj>6K8oxr9p~ z+X4=<7i97amv8}`Vu~Y@ck@ibj&Emq+ckAZfC%s4miCr>T?xY3&yN9L4!q|34urI zS`@Y-syHAs!yNChVJ!+`oUzbgU@c0983i$)+1h`u%7wrS&sc9TWU1L>qrSJ|>IX`% zfAzc1btUJYv*ugfa~EHD<9I23-;4#9&po(nUtgv0osI?L?|ZWE0b$R^jk6xUr|%s$ zuT^-zr$B<%go&K{$*_BHmZew=0we&1g$6^Hk`4qhVLA{nLCfSkbg})A7-(h+<>d|u zhF0_cH5gihc49JDlK;qJuC$`SN(BqhAiNA_WbSk*YoPPf>1*l~@}K$l=t zH7*e7-Pkcz5Lk*gATbH99zU~Oyr35@F!+u~(Y?qRzXyin= zncF`ZM*>5_iB8Koz=Z;XTxbvRXg&GA?@@iw4gbE!9zX>6XUrTAri9#8$UO$zdL3YU z9hgBIZLm!Y2}K@2WM;5namfgeX5j-s)k0h{Wu{JAPlXA6KmQHAZbWJMaJv1KzL({L zU;S>&nvEN!VNehRasey_aqyEW?95uu^@AGKrB+8=(duEY>gq<bm3>l> zmN2RS?~c;KL1WRwb(+f$rie^wIzaVTU|uQYp|16DX83BC8<&6j3PXYaICsP=#|0c7(r!d*&5xDpf{~j zQ{V!Y@v!a00C(WN*UX0>GW*9d*bbo;F>4`nHS#|^ycXB^TINH(F4PR`hk;~25i=R5 z(>)BU%J;+36|dC9Q5+T>;ZP{fwpZBGfnjE=%$R8p>yi-@0(%MH1t!JBH@>%|9F?0RwP`twGweD?}kKY7~?n~HPc zE8n<%+jFyYHB|m$JaqZt)$>QKTE6V@bn(qCQ@(%ej2mYJ+^%RvO1pCS*{y5CYwwt5 zEWBXE#*<&&diFT_UR7MLnqGTe`=v9_{s!Y8Rq%Z+c3IMeJ!h%$`vj?N8Z%M{UesvtKW25D;JndFw4B z33IM6UeyGX4b<1ujfg>6IV4<4unRwK za#_Px?%Ih>Z`_8v-Mz(q(k)|#pE4@gbM6g9yPncrgwDif?bvdffM-G!iFou4Wz2EK zWDmBp6o_>n1CC{|-4Jw$K4yw;(m%xR1Ruhd>$6sd=x(-a4zm{*Ov8xo*o(z$AhuTT z*;!xW>gD~c7g<+w8!|;SHVau|v3>9F@vu%PoR060HLCVt>}({R+!JDV5JMGg6ax4~ zgM+RuloRWMUB?M|WN!ysdBPNVNG43nz;0KWQKRZ0mW+mkT85pusBKJrSy7enYZ^^&)zZdz?|6$s6eo^tE72%?AXfnC^P^AZ?_G2dWz8I<3{ z7SV(ZTu;d%i2R(8cVp{rujdRKvS)}5d2lg6Mb?wj^*6j!hkv}?yTuDDG80ivTe6p1 zh#+)>6EVVs6{k#I=eA{}{=>8odHHgCT$>i6K^gs!)jF7wHw^B)VUfqe799Jsz6u*? z8YhJ;MiGE$VSUd7EUdlrLfJDlCh~9zB3LQDkcb5Fy2_L2Ow(q-DgnT7<2z z2!5y?h?#+yveRguOMGNf>%6HrHU0w+_=T?6AR;&g{a? z^*&gCfJ}wxlhK8>0HAtwNHmf-Zpq;retXr5w{C8~zqYF{@%&X+KlRMD8y~yvyW78d z;!!GgUNk{)e>p|)z4q#lUwQX6Y>7v)4?mG?GGbALN``L*TZD?B$gtm!nU3+m3S|y_ zW~hu4xe$K=fdL8>%TQ9n?v}*exKqNrr-W^O*ru zTJrwDR5DyyZGGrXbUxekh#nGdgo#?G;|!;FZ!_P4?*@p^2ZahmtlZ6aVml3QJ{WKl z{P^fL2(f)YA{Z2z`;GhHSYReItT6FgaixacKxDqdrkcE|`iptXPnc-_~mc_L$<}U-Iut~PB z#|oj;6Ayp>%tP1S^awrR|Kitgo<8sCmmi;B^1=%fniln5fB3_tE5G?jr~mbLK6zp8 zb1y!*ZQ)2@VmAK~_ABf~DAs9?i98(1a74n~fEVpaW z$u0wpKZJh^Fbr|OqoKwZN#TRSlO!T6Lug+~Tt@R;zBBM*sOe|GECSKZw|+rlbwWsh z=)nX0|Ghg~n(v;KrcBZl@Yd$;Z0Q}t!-!hi6M6H$?aEej=B{i&uR?SZ7_f3u72*!B2z^%n+wj`cma ztysQ2&|UV)4>rEKa_QomcJv>A`^R~DbNHdHxtlgW7Wl5P@w!Df-EvD}@6WE-vv|QH zBT9aFS8xBnK4KK`AP!scA8SN1mUG;&&;%{NX#pgZ8}QE+q@BpKQ+n({7H`_t##U{E zY;{os+cL6+f|wEYSSEPz$dMzgv=89a`3hL9Q8z2C3sx4B+YCgp4(v6_LSQ)~D(n#n zMp|%cmStgn_BO1PZV>Jk9LN-VLIsZ*e@YEW_zZUgdBM`!}IG6yTup#T)Z zehGGP!8NCiNJU&JH~MgSz7Ty7b;^dzV>;Pb*jk-T6Znrgf*f%iq)qfzY-h^08-;y! zK>EjpxwC0AqdcHB+=RpZ^=zdO+NLUo3{~E#vQcImG)5WWM<&$c2F!P)=J(69;CFXTF z%Fl?7)Eg48KkSl4F#$i{-LTE&h*Zm0PJRF{r0h!|aRr zC0G_@{;c1S27<0amVO#C(I2{XC?#Rd_`O);$+~*v zxCVAMGV#)e9fU>lC02@F2{39x7bV zwmuu(07hwTl2cT~wkO^5=%fDV%~xMIKQ?aE#gkrtU3_@^+LaAcF7bUwo6>&y_OF*R zIw*m;Xp>&U&f-B(zu3%j|yY6Dul6nJ*-yAWidb!xChK zk5DC4;ugzF6qV9+WvT+Az~JH`>`ypAzbf*D#Y5<+iUeqhkf?0uyYwF8L;@r74^AWi zybD1CF@sE9;t^oNx-FHove+7mVvDyF4-7?dI-yapBd+s62IZ_ki=+$Nl)z`rJ=xO> z3xcpdhyK?SM6d~!XjD{`!KMJwbZ-Zx zZlw%^zrRCKu?w!7Eg9O=;pT`iAXTtNbKXoxr@Uw21GYRFA;2UAVHGGLF(0`?u-( z?kjq}Rkvo#^owqpx$V)z{aWBTvFpP!2xkpi>x*uq~0XYQkUCAc-Zawo`ip z%O|lA(*fhR{R~tX@B+Y7V7E|VZx#zXZHPYpjH3Vm5J5%`nQ0D3W~1e6)D+5)O=;VL zC}P23;eo@#18jxJ27Z6mktj$E5FpT~|J(MS1!{{O1v#}HvZRd1PLeif!bKCVSuVCn zR5}Z6s%`CnsDQ0LnI?=Kr%d8&#zTNlA!fRymK6Im_eJ}Sq*cOr^ptdVrKIc#Y(Z6Df_e3?E#bI1fc+!zDiCI zS{Wv={m(5+el7OWyTZOqZP&MnJM>@3uPD9xN!?+WTd^I@EPa_Hr~lLWPuD-)ScD_F zB)5qF{Uu`)4rTzfA@V_%?D(gCpux-(h8PX7Tz zwwLhG^fgck?9RU$7*pXObwF-$U<0)TSz;3}o`wG%_<p;LF0=nL~&9b+}^( zWPq(F-l`uy>^_VD1oI5UGd(^-mhpB=9?@<)2;~^g0j3|fQiPjYgy_GWW4rB2k2unB zb^vXu1k`nM4^KVcFQ=B7Ym)HqB*ik(UnZt26&p5$`?WClz3=yZ`>}WE-yfQS08b{9 z(HHwC3G?Xv2flIV9neDV2dn!la2Ym0Qcd4CX$TO^7{=zIBuzxd}+$_V@nR&;Mg!&Y3%B&YW4Ed1mHWQOD?PNYimzT6Ar5Hl=Au z4MN@kO-+3&8j0>4$CxUnS|Lx&LUH~y>_v366-8RYPM2oWQs-=pl3swiu)2`7V!_rB zv^=uc6+I0>jn$JURw|E&fiM)cU<{e$(<~UDrpStut&?Z4^dgP263T#Smtz4Zr;~^-u=bd zPY$fvxpI=+5SO^Rwx&Dx>$^?%(si>NE+9(FL_pYn!j{ZO&^2t-d)A~!jQUB-^lwjE zM*XB^`Zp&nur~}m4F=y}iyCePEqi(Iu&Pb|C!p-a3uPu_;zQ3#MIC8_3$7YQ(JT%E zwDl2)2kPGXK}qAYA**H)2%?LKOGgSD>LkkQGd^MrvhQ%36L*0gpbg$=4aS!Ef$Q zq=)6#q}LMvEdAN{XMb{vAz#WfOfx9U4LA933B49lvJ&$`lM~BAHU65E>R@%o{7{p5 zY24z3Whu*2n}SQ!CF42`s}t6RE(+a{azp6%{IU#?dK{5g{B^x;XkPMk=X)c+YGt*3#=JzGt6{Jo)$_tA74PHv8cXBOmNo|L_GL-S}qYk1X(Z z$GPXU-?=>h%Cd{jImFI=>rK|S|LMq2-`X8{@A|^!KW1e^%=*K~8xdrV{5pGD5}inL zF_#B1m+fp3LE|a*^iFqgGSuV&FYXX5!NpCU%jTHpT4dN1u|(xGaE<`Eh}qI$2gHyD z4S^Xr5swI(GBn%rA66JVfx=&mRRN1Il$t&{$I8CMi9%%xxP^)}#|8s=bOEdKcf+_V zB*z7z#>ot?7}88eZnLJ@Ay{&Nk$)P6?G6Vd(?oL^!X5MMGce-G*y9_FXG=*An#EwP z)Y3Vf4u^0y16IoW?h_jI8`Bs(6hU_qs%+M3 z(i|8vTF7w1Xb;DaL1ghdA{%as4`I)&5#eY%tsSn&ilo?H9}UCGJKW+dR}-0y2zHcd1-U|Gec1kyyB{{#`C!9nCJ?SN$!l7aZG9k39I z+U&3{PoP6pjKzo}I`uhD=+Pk%2X?_!q`gNzx__q;dh5k|a5R>12m~a9dx)NgW1M)d zQAL2dgdP5=amvb{w?)2w_1BTFHvW9hqu+adk756bo$o|W+;;LEA1c?Q{O>?mJ*Zi<~qqRs@VhK zZau{jC5vY@DTXY=G^H56&<#E_>>McyR}5Z>Icywyvv9OxgU6(kx(&h%VS^AdqA78% zt)NvxG!$z}mesn|#)7sPwmO@P3Gr39gounbAv$Rj18>8o6Di;VW5E%!p*EcVC{CMM zTaq{a(bGjGh13GM{t}{)(u`UdA2>#~aJ5Bt>6mQ3AM zTzPAa^3iX8^W{aiJ8N!I&i(rCXKLH9d)bF6bp@nQ$g9~xF`ra8C^4EW+*qN=6%5uI zc?GCF5?5*NnFZGa=PeNCX5h$0L?1HF1O~`%oC&E$cmpV1&rcM~<9$ z01OfM3WeOu7l0g_EcD!l30x|@;GWRIWR&zZmiD3O=`mY;yPD`Qp&gF32rONYdf0FmN()?tzwxF9Q+*%U~wh z7-bKA6j{p-z8ksc5@6yd*00muT}m~;vZtm@!)Rk6NfLv(TJo7ge0sc1xwohV(}o3 z5eU?@WT0jVRILZMMwTWS{`OTozW-xT*hpj5!nwfz@;Bwzu)PF2)mA;T-<}AHkl0gb zk>g>QXPUX#Y!_G1jZ~8vaBt=&SvFe~&MhXhB4foS#vGjc0YS(*A`tOt`tBxKHA2!V zx@IF1U9$}UOBi;>GODfFhUGX!sJ412tZszLs=;QC?T6vxNW&&@afDqCWG=B#6UO^* z2nbPPs93A<&KP1SXcd?g%dNCl>vRP8$RP43+1nQcgt635AW-m5%dS*}g*y$>X*`cU zVUL?^R&fF3V1GJkB3WPr1nx42XA8L9GrPNp} z=VZSBxf2mcEnli!^3`l*;OhbOBaSAfO@J0Z|D2%ZNjS=iZp1(!1>T8rWMd{kF~F`d z7LBn@7?VRMUXfIpi0^8G8A*+L8nezELvO&5+StbS8iePfg85_!RA`}z4~|OJj}=(6 zpL`Xc(b&WWJsvJ@%9TQKxt@@}OqvP@NmKnb(ww*&&m8|!X-V8t&l11;HuG&R9_RP_ zXD)V&YZQqUyIFEle!^tKWP6pN%052A}m4Ta0USsyTt+VT_{y}JPCU~s{_ki=0XgY0*^ zu!|SAy}94xi35c&8QUl~Y$lv7!Iu`U2?$}yz|P*I5{CH2~PLhwKhSCrE5}h8Y5pHE1wk$QVyB({3BIUxcWH zzpy+G)iMtR{mJGcn14`LVs}L@dhWf<y=b!NnaPm^aQp*ze;QXNdkX`~=T$e5f+`KyqU zM~X%YLgXurV`)krNM)%m%ucbfbLrUVv6j=30n4*-saUOOrbN|`)U#StdEzSuyR>B!kBFsHV&Lj-;=+-+4BdM>rN#KDA z{D}Q^Qs&E6=C+2dE3E@oMHQmDs*uIQw)z8dGlxU05YNSLjlPb^$6~m}#+w9=5c!Ze zMu_z>Z!s?(70uPh8_PC904zC>dEG;2wB(>0a1cAvr~wCI6eVWB;WI!W5m!#?1{_c- zehUW&EGY;B4s7f}8aqj7wh0C|T;LI-Qv7ME4RqMSe{zE-WOR|2OgBi*I460hf?9$N z4sVPp!tq^H4^3EaAn?((&_K=?cDAzP^H?LV77!eI#q=i= z-)TB*;(s@@ADZtm^Ir31X1>_m4wn#+{;Vzv7r*)G71Penos+m?5$gTj$k}`)cF^wV$=90gVv^fqg2{)ytaw=3?!>vOI2>o% z?mSPD-7*9)Ok} z9VV3cC#Fxl{5{iqUjCBlB`-f{I_Tw(n;!S_?WXNszQwe~%P%%v?B!pZzmDf!=B{|Y z%)Bg~+s*cP9`7}qV878O$*wP*=xwKiGkb+Ys&L>GP#-SzcAG9SZGlZs<}Hn{a60T2 zFlZb0B~NztGYm%sjtv#Eyag*-lK=b%Vnc$l7oMgSjA=!E?rh&VOGaZDT_esXq@QW$E2$@kZHB1AlO2&U0)YDNPr^Av#CJ8t zIPhdDJ(hLU-h>2>zd(@KcVhb=rCyjIQlRflNyk%<$EX@GYvYdS!G2|E!{l5mmff=9m__6wrL@50j%QF3tcgp^!!p0&`abg&L%hwWV> z2o|zzG@C3iMF+x&)s6+)qFQY*1P1|Oy~Ab$6JKtqgLo>bVeSso$x)NrqhiI{K=xY&6ZVf$ zvNHGYaX5dTPO6!jKpu{w%Ylfr8*GA;6dok=gjhRNe89^mvjGOd%@+m)h=&iAA6O0W zVI!9^x?kI6wp;AVeyn8j$>W-2yN0>x{BE(}pkzh>L*m_?g8YxuRrK#0yE8O{#Y*Y6@Zu0WWOqY52M$<+wZ#TEc^F`)G(RmJM2C@|Yu$N{wq^`eoIx*E*6xmJ`$QGy; zvLY3B%=T=D?=0-0XtF!{c?|y&`dusLI`LTq?iCXqD&HC&Cdmpc7qoEb%f{$bkInM) z&&RA6Qyo_1u&2GttOk*X(F1e zsRHBXv6kA(4TZ`Kg%dOV9+ytCjgBP73=_*eGI9#4 z2A;-tm_{t{gw1*iDOaFm8Yd#=#z9H}S&(&dkitVY`*t7B+D+ihGsi>1l1 zPy!U5ZJlDAYMo-|fQB(x{02`|+=0T54ukiK`=MdD`$GD=;aNa$maMGlW=m4ibR~}n z5JX^xPcBfK6-Cv^$~eWV8mSrnx(r-l5T^&d!76iDCRO(G@~{gU0CyV(p+|qzpcv+v zDa{5m;3|Z$2C-$>%dZ&zmy(5$S+%MEhK8o|7c~PL7{9;k#_8>3oIa*XM_evAi#SG{ z2ijp&U^hsnB@i$yL^vjGCqXzS3Np5EAROc9@5ibT7+n?i!6nP@N}F{Gn*yRfh->KD zPs2V$jAUxk>~^{*TPS9ALyTXLzli#V=@DHaXbFiEOGp8h5DMc0Wgs)YyC)IRL_tD^ zjv7{&_%bCNUq(Z+I}6bmX^jdJej4EY7F;?KD+{1VBfKACD1_AGDI~T+Xj%#30zn^6 zXDl7`;ah&rf5%KCxAV(RN+X|r3IWJm{>I2-CvM{({57HgzuW~_Tn1QlL1tXz?ReB6 zn`PFdyW-qwF3C*piAOo2r@ANBWsid-v<^W0nMhjVpfbv{$xKl zLf+Ntz?4f!j8F6?%Eo{Uabu4Y7r!|roh4f1(lwe+9{#|13+$BKf@ux4iBm;sJ&DRAj=auKY{ zl#cOaAvhg&>V4uo@EzjJY-q=QY8xWfDuY@mx=?x0IF0 zrRGvgsiWLE#Z%(7dc1^}JaoYlDv%MBL&be8k_vYrgCbBEUEtl&#<4dw<=FC^A66cy5mF4DhZOfdEo;9pp0bRq^>1_A(D;Ju{UFl}eX73fsHKuE<+myZLCp^DU zo;Sat{LcKQ^L5W(ln>1xIzR9f0HH$N$qvO(cOqTErNtFQfG;r7xo%hsR-6;!-F_?d zRPh3=I8)3mtT>yk*xLzy6zGp2@nK&BS(oKtyO9lNVZqt$_PU@&#h@P<2XXV56=$y% zL;!fGwphJhHD<%vp^i1+%)P>Zv$-&VC4g$G+HnrTQu{Vuw`V98Lm<2=`(@ftBlm~>j4;Hv!<~a%WoZ+v-A1H(Om3JXe20<>mVfFcN zPXHJXn(|nPhEmD^3Q!YDDf7bivy9+yCi*J8o(kR9ei{jwuO-IS}6V63qoHp?o;yh0{u%N~E>q7I1vDjW+ zr@&fxNyPpaW?hsqX%@?VWn_fse;U~mOrMkx*~(AwpGB_fpHaVrT|QEK;!AEDKczk` z0&)f6S7ijV)dBAf6U0s@tvzmCJfH8LAAgSfoOs1%hb4ZeFwZrbo3qI*-M0il?Tb%0pQ;LvVV&UJJXRU}O6^7M4!qeVxZ zJol#BuA7=ajyxZ^ie2>NUCS3t`a$Gs!+xiyeb4#>k&%%{WOm&p=YBr{rwJzA2HR>b zjDTC#Gz86gHf_z}h>S5}L^d682qglv+35fx0H+ItX+*GWe;m%Cm|f!*y6iHPR=F8Y z$p~_AG$ulJR2Bqe%{^3z&5f-J*`5~1S?w5s6tYc;6f$iB^jB;b^0S*ag&m}WfZel3N7sv$ z7`5eiA=s7zGm7IlB#%ZnM1(T%7YJV>aTiP@1TM~y3$@_uW`w_N|vL*xl}$! z+2r_+^Gb)=#tmjzDstBG`Er#hY_4_8bXsrYx68MfZZYqWA21m`98+hafkXa*n?X33 zXfPvcwl}yM7;J@evza`Dz@3@XP2;rEGXTSd`}q#==_WxF8I-$8yX_!ZL-vXpz%D>B&n{AZUzSQ? zV(*ekDM%Fi1#4GmAmA4mTtX0z3?aV1oDPW~V~qKi>^>$m6QcI^aRY54rhu^YDOf!G z8W8(Bmr9t|Mz>3gb z6*iqGs}ud@nCQOzZ_$>1Y9RB{;;{zfhSrbL?w5Dib~yG;eAV-6 z;_s7xpY~eu>r8 zfW6LDyX{HSNP2@rVvpf0TYHS8IF9QYlY)*W4olF~2YFUVm^BucWaY77Zpf464DqbA zP-4iMWeg=}73WO}N+nsiz#CdRtW01(?6V-2WI}9mPEn9e4V4BZufv_rQe|8cZ2oj9 zV8BHKtg-7brNB}$TDun5@0Z??DrAH)?U(-FnRnu;cF*iy4GJT}tQ z^o>(UMHBjXnVu<=d8QZC;HV`h7;cIxacTye_-8+3YhQRSa{DvSL~j4(i)`)Bwntuh z=mA!6-+ip${s$w!ZM-P$@U??)x9!_}`JlQvt-r2!+w~ilU1fOXaAe!Fha+k5qIYIw!Qzt4-U6pvOaZQ-A``6{mu?rdB*%r zB3jPeNGJR_odsS(3j3LWmo6L6=Xw5Vd_KRxZk&-YBWZrp*0j6R43oW+12fX9yj1}> z;|;WWTLUZ82GU+NzUKMB_>ujues?Y(XU~T;#uR%EpKV{pJNcXT-~0cR_)*daffJah ziX%Q5HhG*z_xP-ACmoUeS)QGltx)O<}hTkL@EF#CX^SY}qP8TsFpFrZ=7pF~a z7+j9a{DwUpV04iw>RUIQ1NRET#0~^9!6g<3mrX)0*(Cho z(*4TC&z^tpNctn+>AnB)P5t*pI=Ok;LNQ+ef>Ftj#~J4 zN-!J}`dc3nbVQBZ1YIl$bSkqz4u*j_95AF4FuCV1L5)Ok{DLjgsP+}b(yD8T_?A}W9nK@o7!jY zPr2NDMat{uR}lnBiOD>-JsZQ+(JB3*bR-2mt_RlTUz(PiUdS6kz6X)YTH$Js|Fadu^^8EQKELt`3H0dy7;?v@Nzhvsg{;RwW&W$s;F@s5CI<0~MH zn2=+Mn7btgJrF2-kPRFHCC~~qz!J{m5&>1C7ER=@VZ5+rT~p=aRlM@aHM>SOzx;zg zMBcyi>JJ}%XQZU=hJ`&px%Z+AA5s=M*G{aRc-F^nx2}wQ{@ZJgUCidQ@3IG<-tqGj z?<{|)dGB4f!QqlDEsFXR^GO-fME;9Wh}&hC0%;TIXa-wNeDQ<{9%!K(a?(7GG}=;v zqyv!KLl9@l$29*`5Xi-Rr^!hX@zCWbnwi-DiU@%wB2i$KGZV0ngeb7; zJ7fIB?ieftIuINa0>s*`_z6)F;qi?sf(TWF)ZGQXd0(VOzfWoS?1?bY1!DLtVI}$? zU8$v`?bAPoZ_$31_UExc^QCr5BFjyzNvsKdVE<^M!7`D3Px>DFuF_{d-_~RAcUBTjV(8l4e)n$mg?T9~EM z!`DdpW9Co(7)+iY!`^XC`Ms{N3Y#q0sJh%RM822ZxT^>-#9bhKKhw`FjUR ze~zyi&eSty>X|aNOv6o?y^s@&`Y1~XC-8)=lTPtbCgu({6VaIyrL>7*TV64GglS!3Afc23IvQEnp(TM zJPy+3C!={|CX`_5vk;kaBul>JU&nyrg2-SOyUmpmW`a&KFE_gnAXz53SNf@iLJ@}$Jrp)F6d z#*QC+e?{w+KR9u+c5B`J1D8Dnwg!`Wq+?3CvIorFX0gf#r41JTrCHu!Fc~cytcvwZ z1KTiz*MUWtWM8sOW0yl|39PdBL9{MBj#Kh+z}pkp9UH7r0LFH7XBUKx$nH^5aiOrM z6$b?)u_=YVA%ip0Ef29Rk@JtSn|9EBM`Q!lWIcPCcgQcICKIV9kQZf(!cL3vSLDBe zOa1mGVAg|ycMKNK@WCNDY!R~g*lKz8Zp40S{kD$IZQD9Kw((zg-h6W>;wMi+4&fun zAv`aIq;sE##HVyFOfyPSW;mJkdpyunk}TnP-{ki!pIG=rOW3m2B3sx50Lhg6DbTV% z!Cp|h`8mKt<3%62*c6E;8|Y||Bt(wMh>-gOsj!wRwiC&Iu>+K$VW}v+?1dX|L<7!~ zhxul4im*vP)LMwm%&_JJzN%K0(a8E}y%namYdmu0|0{1bTo zfITD2yG&Lf$3Pa8Sdu*|#31YzLC}Zf7I3bm^BHu=+!X?(HL%967J>>)F@Zf|vGX9-OM<&X{%=hW1k&m=1jnXnCWs6o+h`vaW3^Fr(%q96! z6&`9hTWh5Hh;f>ssZ*d&cB4&U@wMb5nmCL2%vBS_Cp`cBXXIm)T?YA;@8Xn_C*3K^ zuASpzIUX0(4C!L>+nkfL;E;K8Vs@gGo0n$~W~pBIlZ%6fO;CL-;EBsJB$WrULUOs` zvApc^&|`UdD`1s~OP%=}J4BtpeAk%1#P zO{Kw(Dw}7?@(rzuxpGCYbX7sPF^Q`!wY-hjUBebmXGrL;i*;O6kG^A4A^N9B^v``7{NPwOjGoB_&4=zlOdk%Te?T|+I9!H^FUKe4 zGUiaT(&L<24sW`uIvtOBlFL)Fvg6A=S;p-0oX40_ZhQ=GH3u+wodrL-p14$kOHRyj z@&ELGA`?Q?Pv|8dP7dktIjV-!Pwyp`;N%=OXrZ^0F-4d<14p5Ra9%dly(tLViTEbf&{d(ShN#LyN}mj9T}RkxP7|YcIW)>e>JQ7@~o2GZyXNInsTQ*vE#1D zZCBT$kM72~NHkDT(Eg&? zOO^sovO~Z3`8EZ!hhR}$itT~y5Jb!d?BzraJAgfI_;p|bgr^{qZGCDQK6ZvH@K8Pv8 zU>ORcVweh{Y|vYDjXWqu%3s|xxp?;SMd69%Lqn7M7t~)oSqXjp*2VQ{x#>ysMk0o% zB4_>c#>S=B9%s*D>J9?%w+MV=k#3<8)pk`wT!I<&$HO*rh0$s$S1OFOLBaY-$do+SW0{joVIw4=32`PLDH2{tWwcKwg!Y$`_a@)>66?b1Q5`vO z;`7MO&=BKN54#6ztP$&nN$L<#nQbrv3tOZtnPth`l%*)ftcjd$=PzNMJ!p`UEiB2b zt+wxL3k&$fB%DGLX{1_#|FqPQJzh|Mj7DhR(z**V9;~~9tJZ2rHu{||L~ViQtqe$YyVnxWom$Tvayh4jwD*dS6H(Y)wF!`Kx1uS`2JMP?^X$}jRuBQ3*h2CEp3 zz=oPEtvmUg5mX<<9I&KPDMmBd7{@+~V}%99=`%uA>5b`+3L( zoLr2v9B^Wn#TEEiwkrm+Da9W!eF&k5`-W@^4x+)Z+h#9E)q;V-z>I(#sP!M@Eg+`_ zVH%kr}RM4K%eSCz*7Q8fZ^C5~rB6ARj&~U)+P%tkp zm`hAR{KxF1bM5&7$V9N<6Si<5kmL6UsTD4#B;`0Ezz4Z4gF!6kppn9~`V3I)-0v6R z?LrL8M0lM7FHMe%8gYzc;DKk7g>@Wy37E9KI*GtSit~M?_Rg<|r>?&6tlJ8^_;h35 zSxgONRaeifOt>|5>*cpzKW+T>#te2@nbH2`aXFFaovmIJzjpda+=xvoZ)isbjJ_q|$i?p1^a*xc+lKe3Gu0HICbXElvRV1ZTeirU@ zFE!18P|#7@n@1M4(yf{7KVrxZDTZn)C@szoHC`D%|DNK z6)d0owDQG|3+CkA@co~HWiM?$`GRpf;ELpS15&nB%t{4pS+^!_P39fumRq z_xqFlfdp4l@}zvvlYBcg?Ajrsx;<=@larD_2=e>0awt8B^a*_XP?jy_Nqz@x;7ZaY ze#h{*N2VC*cLIL9P^3jgH}p>ya|e5UG7GHHfHmJ z4@$-Aq#zp~LaZ@uA>Vg@cwW)HGN11@fx zCn&|6aEX$Gu_qMkBrZadA_s8QK9oh$cH0kQy6W6vfGn?+qz zUIwVKG3S=C&oA`dCnZ6%g*GCU%4>_u3DIhGI$rz-1 zB!t_5Z(+gnZY)@vWFC*BM@+#I+Lcd0$&%^%m10C7oI-L+|3baeK+StFvgAg#LsPKC zw?F3JV+Z7Uz*Z_j({7ld#eW>?Pcfe{lx&028 z$?bD`jB!4v7yc%li9EsQOyUWCM>07%YZoN5Rxf$i+IJY~ljNpAQzh_u|P&=aJAA28GKt6*{b-IhKw|-E;CWLg0 z1?O!9I1)@SP@8ZcVeE84;OCEe~UgQ&2xa5{4 zY}%4rmpuC7e3lsb)skD9BEMMD&&uaVo=sv8ZDZ@Uu}33|C~S*t+ZJhJ4@H{z47N_d zCVK&AQaWdAej*)o&KAe)DBvx}aZP~A=ovc>XKW~O(h(aSc`eA+e(V-a(i#CL`HGwm zKgtI+=e2vBb^K`Fi{gBtsnL&~?j*GNeYHg*Jd*1?=?55NkTy73qERO?G{x zbDJ@n>M)fL$=71u7^MrSH-s)=I03UAGJGMP3-{|B0`LZAtN`J=J?!I@EA6w0J8I;5+2yY7$_G&8^D5C zU&ZEFvk>>mKGt$0Yu)`hs+4}>Jv0~f`^GM7i#z}m&<3LnbH%jF!j>7kEFLz}E{kis zEcURf@MN=6!D!^7yDW%x6Vs43S+!^0WzjC48_={c?U7H}WqGHt1jZ6J$}gUX07fp| z!R{E2JQI0(cnnWWL}mFZthM0#8tyO@qX7Z!F8@Ya&7mgAZAK`0<4-v%O*nZ-`pzu8 z1qnDwYOI~5N?}PodBX4tgr|#5XR)C$2uq+$NlM6Y09jNi0nLH<$5($Swwvx=;i~wj zIe;Y_fA{_=8?*>XdTsSnmwxs5$QsB8oQJV>GPfDhi68bKC?j+T7AAVnjyM(kKo359VkgpZxU5T`Z zP=_wm|GNl_P>vDzIpQ~-p{yNLHr~4ncMHN@sON2X_ZGvSCFp`Q!3{_!&OEA z(UGnq4zo_;7p3EL4_^vex-3=kqG((hlLu!DzA2h+ME)D1ag)@>VfjFmX$GMcTV)Zq zU@wZOh&!A{`I;!K!4gf})@U4bO3$6qIOvp~d!un#8s~X18dt{TF-UgLi_vr=@{{Ht z{h6dmo_C{hn4FKhH5#`_)n3?S7I_>d+1rTPFhxdt?a50-oO;Ne+$!P_cSL%xh>M;~ zzC^@{%;`?vBI1~hJS};9G_Lg_l?huyq7SvW)`wbL>q9NB^`REm`cR9Tq*WVa9{?wqzV-@w(>TjdE<(G(bD5S^Q<)nE29Fguu+?l^f#Jz}n@>@haeoX!Z z#NGK?+Y`s6Cn4_4|Gvl{5P5DC@zgPSf@9*DBL6)iPo9Vq_l+szMO?HIW`d~ zeN4JtZ_9%ctak&dCP^iTHA)>)JMOh`Jg@6x^t-cj(afc#_(l0>S{U}F8 zx!owUQ))%L74eOD))V`VD(SVKC>0||2!Yx%5p5|zya8o(;hhe|=OVsAl+h}!i{_n& z5<8GePy12FUi{u5YD)dkDcaHXFYB`!xqI;LOypdJ?@%r2dsN!kHfrTi-P^^t=qvqr zvlYKp@y*p}>t?)5y`tjT2GoUmPDQ#t9yRDARazjvLgi53P8099i?`aP^{55)G`-t~ zCt8j4ysAj=#aF5SV5=QhYON~i?V$JiP>W8y*^6GN6gjCy4S2E!^`gG$`37v1kI6}W zJ~pokpJTP6&OP`VwVc|T3w?cRUvI1qQ}MkrsdP+9wP^2X4OG-q>&F(v`tWXxXwQ04 zBN|UC`F{ex--Ps0SgaD?*^Do5K>G<=SK}XzGmX=Dga&+fm3Xd73&oQTq)Gvs35|Zcs8hSBUz^BBeXXK0qtcQ%Sz)0ecFM=^pW`c18)95jaO zQ9_@1MsKYW_0XaAbO@vp=nZ;Xn>(kb&yn) zNGpZr5#f%pEuaxqG3ThCT5&ydpV6G8x>D{|^bz3*9haTn_f&qDXnhXy=i;r=8F}W? zwK|+$v;VYjM@y$M)ZmlGwii92!J9tA&(s2~O=qeX;S?PPs3lrkXq?t##5L?sIkDz}Psx@8Zgy(3aG|aydeNB)?{k;ia(`K9o!<3V-`q+<2XlBuDM7of&s#a2`CeGQt|B8_^lF9tO($*ZU zeKeOSr62QH!{ej7IBGe~Xu=)@n^AcA*JF!Zi%{B_eG5iggFvm1Xx?gjA?gneE6~dH zFJR@HqIhS8NsA4B@q zden#J=4!xz_M6~MhY-SB+Fr01HK*s=3ajDdRRXJNutY1nzH8L?Xp~=r@#6^!whNJW zAO#np636@D}wB z*^s5AY)WejEdY*pi5JEj%7p=Vx zof<6aa7JI%zDHv_wk|qUkM{L9=ZIE{4q=N$J-%5(RrETIoK`RDJFO4&H%~7abMrwJ z9LW;3v7=qB?cUJccfrPXbyj!J#_pb$zRvCq6V!yg1ytnRvoAWp;5N8B3^2I64estX zNW-9wyX)ZY?$)@wyTjn_?(UcGJLiAbdGEfrZueeErIM;$mHn%|l1{p6q=LRa$6i)k zzjAqMePzA2q}>X9cmJTF^4M$xG;d|OF(q+9`@1>2wNw7=SZ{Wzw8073mL0sVSn?Rs zZ~n)c0@7SqdSL*`(Rws!{(Rn1B4c5#S}0>>V_sU{{PgftwRIq|wxv}(W2w2cthT;D zI>HOZwdG2&zOl8lsGgv4&NEHhZF%k;(dvxPoTxspP*HbkX}Ke((KB^OqL8by7h_s&3t zT%CjCLr@2-xxI5|OUE8~J(1OU-g<#?eWkY5nHliNfqSf(0rW~)8 zXrj}qw%c698Q%%d%d^62DZI$!@!LW$G&Yn&Bc`UTK2>&5==*{@424tUUE@8*FLXEd z9MPc}w3jcq4b2dAc*?cokG~Is<}17q=6|2HayaN_?>p?9?{I$Sk$Su^FGJ+HGnIE| zxWkKhIE20|n64=dAsjmDQ-5AwK)Zc#*e21 z4*t5bT$1v)m*7knPyS3%rT`I3q;}Tl7w41!K}H$(bT1k{QmS8BYimmjB#cOFi=gh@ zOc>agJY#@|*01@X9;e>Rp6MosfOE=aEPRzI8nX<}%M>h8m%*yT z z@@uP03qle$2u&CwTPfz10_VqlpqUO?B8)#u2ocF50X*=3@7Am`%8u6Qxs8nkqJIH{ zldF7oRo_@$-KiCcS(&`y!NS|my5^~J(C9)Z2C5;j%Szd|rm3t##oe`34DH1q+`!09 zJz}z*9K9!@RG4;0|#v470=$(OlN*%`eqFe~vc2{+F%lcn{aHOj~0W61*a!2`QWvNcqTrI@i4P zYTdHFp~YZcCtioE)s2Dn}RtozvQPd9gkdInii(^bB z7Lz$TOUtfF@Vc5Dru*aARM9y|bAa$T>2dTDq9avi3mQ-%i!T2GQ_Z44K|M@sQ0%K_ zaW7`xbaji0sye-(CU$0Ek#Xi`msliH)k*(VpadBfBQ%nzh3(&G3H-)e)399e4EEjA znB`rb-HdDHYoP$oYpkV zI{*`h;Id)4JaD?V!Hn}%H@?ZFA0L2;^w9B6z*IP#4~oCoMFud1b%WWg129ATm2K-o zSq67(Qt=sz={Jv2=ZBAe-lRmnA#I@W6sMdkk|HXFY)bx^`aadHEnOI2K`i^`Tc8FS zJ4qdE3+x1xW%L?EHxSjH7;Kmj1~vMdG(4;UysypgXh5838}R+ivaN0AJJyr{KT=_i zpIt{fboYIBI)||g;(3C7knsEFsQgItjN(`N1EjqosE6StyW^?4UoMsDOsA4mcB+v{ zsxY2dp=F^K96dWQ&?x9;f$2d-);bJA*n%$jTf{x1FbZMa37MrZYcMM)k)+srDqH{w z#1@*`J@Pi`+-dqHN>4uDo6t!KgHd=c$}C9$8|;m^5x%wdJCrP@2L^L*uJXaV8KjtOTfpFCYogaG*F{ zuA|3-@NGpC)VyN7!O>EL-kAgn`L+gwGHgXxXb!Ffq@u|1MWUaGt}c*Yk!L7gsqVNc zSiUuUGF@P|(7uuhgnD0WZM44XQrUKpVUUgqe!4{Pc_JubzP}??SiiGN#q}j@vwY+9 z##86`;7h%8*U^2Y>s#LjX7KjukJwi- z!%DLI7FM4sj4kT`p|65C71m!9I(;r_0u~ICj~%hOTnPYe^eIoH!jEHm?=uR&O{$n< zK2imsb(e_+dc3L6JGVQ(Jzzc6Zol)Mo4LG$fAlM8DU{Z2H3vAaHnrL^^w~pryK&B< z@oM4>BIiQ;H(`D{UHtDzb`K$6+X`C=A;WPI3l$)71?NZgy?Gt!an|eNAq1O%eNk-* z7%3tP8g+7X{}U|SxEh(|0J~;`X6OL>w#nUhh}v?g^tn7<4#3v^X=QfV z!B;^1_kx>Sn^Ky8(xAU2sNz@n60#Zw(C~u5=f|rPnl|4azl_YyxeBJ8G6-fO0O$*`il)~;f z)-S3tj`~t&m(y4x^U_zFkvW_F4l8J{dL!wZE$CO-y6!f53|@h{-9b||n>z982{k{k z&qY(*{aBPH;MION7f$iG`=KcYdeEf?kL(xzi89IG+N#)$?+=r{uZgtrJ;Iz-QqC|d zEg4ezCB*?LTTKu)9P!A zO@gOG(bTs!!)rE0GkI`>H6EZJEL=YGs(QR>8peFjOf$BP%WP63H0fdHwKLW|^ZMj2 zONj?1eSLtOdOWGw5?76v8pAzQxjdRbP~g5KDb>Quc$fl7AwRH9-$|(U4STft^?Bc9 zIIGDl$UEo&)Ks&iYT5)s51}b@cyn4c_(pMmFe#tGzaJ~ z2HLwxB9Pkg-ud3lagP7K^Kib+AXKniJUQfWA8Oiht-=TO?fXc&%+wHT2S<+EO0F|@&iE?Y5mYY=@_qnOM z%ev$6#M1pOuDdvCxu^NLYM^X|KyTx5N!9ReIoGFDhw9I$gQ-TTLv4yr#unGMcI=nC z=K`MbnK2V=SIh?k>soM2b*Py%)9)=DnH0ZVv|2v4ySyxQog^Aw_l4XC#)P23eIFi2 zt`crxl*0(QVopQ(ul0H9_^Ptb{_xw8anTzU#&0ZatS~&4@s!nlv_A?f=?fqRIgJ<9 zvAmN=-~DPTC~cJY6FT9=F3+^RuPNY>{hnU|BJC{Qb4wjpLyYjEvjs;TrOu-rWLDCG zEYPH#4&MlXQlun@M_Yt=?PBy77bnn1;xR`(ndPK>(JdvBJe z$+%W&{ze&?&5QDI)f6?@4)Z|T%yyu=JDt~LN-1~H(u12KcLqoKpUoBuEwi5Wz#jey2x z)~3V^lEwgQ2Qvp(ItgM15rDnXry>JuhkvB(iM3g{KJ_W-!ZG|bum}8?CZT^m!ZacP za|1O;Wdmz_S)euWvooNowb`dm05L1$KQhGp{Qt4(U&eook^xwoI{YSP;o|r-&CJpP zVE4JUG;jcj0REN!6#H+FT)=eWCe8f?zXf0L_YYQ+L@+D-q!VPVbR>S7MkZT&<`?x@+jlX#^yqJ(2;N?+iB{%j zsy!PyH6>Byj>rLXUHiz5NXc!S@&L8oTyNZkgBLRylW^R7}eczRrunlhE386#WHX^vXXhb+jc%d=bKP3ZR~lSdVe_n9fU8 za`e~E1rth5M|b3f{`9Y@j=pf(R8!WXnp9I~B3x9AWg=(Ug0rE^!oKWiszSa_A*DHj z(x`KSzVo5i3fXI6q*PPfigf6Q#&@ZW0oq?PgyPv1HpRk5vISd<5z&8hpaur9{cfXT z1$A3eM}3m{RuMsgOG<=@7E^S4(-}}1_`zZ{Ju>WrL$|Gq{H@(SKmZ3NXe_YK*H2!NCEL>Xco-p?YuF=BPS!JA62XZ@c@ z`xhGj8P)$!s0mAo`~#VP_AwLx-x2hAxU#Fg1Heks+64G7fRzBIW}jeiC8iSk1o!6y zYzBEdV}RX1u%-GBY(JUGjy5)y0IN?Je*%x6pO``Y^ZxN)3^ItQ6EiU~GQ#~E1^=Mi z&dkOEXh+P-{Qt)2KLY=FqfN~8`DJHhCT1gMV*i(5;v{Bf{Fh>8A!g?IFN%wpnd@JQ zg^BpH{UgA_@=yDhVk2f@gZqd2?C7)oOR*EP{+F!o{|=0QtoT1q;s3DnU%TZDtUlxT z|0nJ~W5?9~)7F2F{P`2;0;f&K#>PoZ$HKuz%)!L*=|3}v?q`@g7+9Ja30a$30zNxs z5c((39f-M@IsX&&4#afKEbPP#!Ui@H05j9y4#b?yY;X+9p97-ykA%j5bJ>}=KE<6( z)y<4QRooWBwW0pJUF-@+tWLm zXZpvc|J&5C*5>M=B04Xi<9M>ZVi9{j(cz4m>LZAX+7o&76+D2V@0*$^EIEm;RV57N1{RXKLTnRv7(zTU{Crl+p|wf!I*fWpdlI$jM#Jinp)&U3zD zEF(ozmZ9$0TDM7i&ZF=!);2-`z(S_{h-2y>*AUZkHM0#l!hOMh%j$XY-798nl&ZZ7 z=bq1ND>bkIy4-Ps2hHNe!N{evy|n% z6mTGpEe%s%UIJ`t@LQW$JZnAjw9+*@n_56hy?iwtkeLQ05G;(+zY|vge$S4mNslHw zyj3O;h~Y^B7e=CP>R%~VFN_Hub%s0k)UI%e0$SN`Gw3O^8&a(1+&RJ~HS=6jG7LXU zgg3pUBcmLS%2$8&!rTG1^9PYHUpioCNsFc>Pwcp2xcdpf|B=?DsWccR*D3fZ=q1e` zvfQ|6{%U0`Uxm9mi=YPn5w)n-5UFb%C2_DMv)b7g2~HFNiMw;ja>`767AqzJ`0b27 z)lfy}LBZ2|3>YZ?{S;y+b_`MM&IB>AUsXPWvxkxkU11szybP+=s8>Z~qAA ztm}`M7EO6m)1W&4Y3+yhNJuAVKt0f`?d!8-&&p7kC?)|d#rLJoF?Wb$mVeMe))mZ< zy0UNdPeAPBU-4z4`7Eh<4uqd2z$JK45ZTiUuc^J&E8KqYlRuu{;BYV1IcrnGTXzK;!JcY)DGo@z%j%relMH?Ob_)J`Ic^!UA%v| z>j3k-J^F|fj0DQaVECPs@Dq(u`#4qCS>j&!oohgKN3UY0A&6-64#MkgiXFf>Vm+Xw zIK!A#w-kN{efsXMc*#$2hr?I3JW?-f$5l*~9Ma!8W{lLAG9r9$TU(pQaI7U}6t&!oks4vs@MrRdKv73HdU8x0@)g<`u+mXm zHy8KkV5%+Q^omQ8u07H@{2VymqN^{~*PL5J7#3dM_%O^bCL;+Mqo* z_Zr6T@)O#VxhhW`E4a-{5q8K%NXtkJrSM+V!kRYhBqK}Ej-k8w3rdj%jO@v3|Gj-A z{9~(2H(OswNZ|K?pa4KVGCn%)P~7U+(J!1MdSy6yDHYl4+emq2UY@sKs{LCl^<~R1 zt8e4_9To{hIf-aIWkM{C;MWJ4KkQkmnHr)C!# z6gq4rc1_W;4vNO~wkY(HtC*3A^EP1#v;8J)DYA(+wM+3vY{NRACFVnSTjQ?B%q!Mx zuOtRhD48f7AGt0m5Px*-D!d!mC3^;*13fZhng)&cEE72GQ8d=CJAP1t)hMl4eOLD5T_q zrGEh3Hhl~4ka?@#U!9XWdA*-%IA7lNCp<2@Q##u^RX<_FaE5k^OC2B4`Us@%sUw19e)4@o9%E>5WO{@{*drtgr-1bl zmYLq&L#%_1c0*kYi;tpX?}xg&O8fM>N}z;hbV@YW zuU|pw9(Ly<9Y3|_OEb1SGvD)YYn~ch?bCRa`iJ*l)$E)F^&exeslqJ`z-asN%+?1_ z#X(Uu!liZcm}r(ds^6=w)Rc*#_!rih(!Z}{dPNQdez!GjlkvC?pCX@lY;7yeOI0_^ zx_q2hb9E9ox33+F5sQVXEm+oAD!8fUz&nwRtQ!=i)DE!FwMj0ZrUy|=u-^R+gXdm7 znW7txk*SYT)U61pZWL_G)}4`b&ERqV$}HBhHwHPwE|*#_WUkx$HP~mCFOFqL@YUqsScB@% zB<$T%lA;_w{}BT6!QzBVuh3l8NY%B&rLV+yl3!l#snhYqEnZhXBPpHRL& zMaHC)J~h~6L30wjUcsr3>dcPLFd$P&7TV0Ef#{tOL@+D;IaBC;6M=jEwvnp3RK-F# zc7$_C7NWUCwr;t2HYNkpoH{j|tf!`wmsz}SQEITjOqel0v zz2DqW(b`=kI^-;jk*yhZz8SgiXG=~kxJv*&&TrMTyww_9H=4fIH<%C(V;ovuyK}=} zlI=e6MftIQyfnD7sbo3k0G6Q`BPsC4DcYY>os^kx8#wzwG@Q6 zT1Z>SxBqqjc@UaG;oM$h^pUgpl8Asmm71=jk=zmU2z-R0zuW*LB0)?I4HRL63}Q{; zfNr=F7i}6-F$vjOafuWs}^6k9~$ zwQ9}hS{%#N%26l_Vg%|cvv?zrcXC#Tp=D<+yOaA;EN# zFq19S1+BcD6sUM?Km%Q+1^m$SbQ1{-6Kth$^KqeHy-r_CS>0*o-?E*&yNFA{&%XBq zyFly71y?#Hf%QCnQ(~@;S+$IY}?(BI%1bBYum#^H;PP-5k5kYe;xCkVe%n*I9_ngutjQ?mL%m zPd+CB7tNPHQ9BjB3BofqiAZyVb0$~)9*@H`NJZ>WM z^XyiE_qq3ePVy3m$3rmhNWkrftH%0KB4O{u%i3HiE!d60<|wNq<6_)mY8iy;FDcS` zq?+8FaccZvnm_SLDZ4wP3>sbCYq6tpf1t!DlJYL+l}bhSJ{b#24}DLmml!ihXkw%1 zohEVX;KGU97FRjyMMWKJu?#rla+yLBqQ zmFcD|apT1B^`_VR1&f1rwO6plaVi`SlZ6Uhao3UZhdP-SXW@wZUTQHE2P>bc5wI~YW zB_!5^M;wUtu5OQ)kz2i2uy+}mnFFF?HbH`avXk;R$z#wFSSqoo`Vfv%uB9IAo13sm7;Qb3g$$_QP)=#*tMoQIBa8#Z9ayZNCd_iYj675`K5sPgT;?7m z@Y0u{@*)~;_jph^Lsl7K)B-;=P^(8rui20&6)e5uEue2A#B@b)P z%a&87I4AQ9z6ow9)IL|-D;1>@22UN&g(dIRaER`%70kp&07*s!mBEUQDRpbTUwZ^G+vUZVo^#L2DqZ7 zmNp*NluAqmj4+#O)_^n{BrQ?1$YpEfYS#MCx1A-OH7{zcj8-1m&C(XX1C<*}R%BMj zR%lm1E0&Yh2aonM_S;-fTzg!1T(kE32egx#S+xYepdZ1Gd^m}T6p)IcWuV8PX`w;T zmU$l-%pEy>y4SiRy2rYOtz*ubzc;-hi=qAGDo*qdvH#dtO%i;saYhb*EJA}uqEBWu zzI;?YSt!@;G+1jv)Yoshy<5Oykivq(Ku5q$7Z6%5ebeMAY|nX1DZ29Y_$I$nK^HXf zme7z~xz)Kl`iOh!TYF^L-X@|KfG_2@Q1D~Ohw8fa_2IkfF-5I$9K;sHDNP@^7#ksrhZxTY}%WLPCM{jmzcqLp5+Er_S zt%)XL%>#^sgG2iKIGaO1SIbQ$#uN7ZU^Pw%CXy^vj`WN$*%4?ytuyU%&!UpxM5nyZ z$%A73c*y1F%7`@56#hh4k}}E0FlhP&u}a#v;qsBQR+L$@cV~OY&Txkm)X?O@6<@vs zy%+7615K8iZWmWH-rTGXn!_ZrGK|Ze>*9^WjZIz|h0&krX5Gw^->s8Iryx5KR#TZ| z`g&}k@sS=gR4G1&9*PUPfn-a8P%1!AMy@H@E|*-P+-SXx#2P5q-FTna^}IP~*KwHi zOd4xm@p^m5V{!hxJub{&Wp{mT%I_*Je>h*wivOFK!sR@0{jT)ubpQ}0kd%FMLS_}! z1fyR8O><)+1{Juh5=AkBf@`6?3hTFYuer$oUb^X^OSvSMF!EhhWXze6y6Ftx9^v? zeudgVU4f4yqPD!W!c{$87JDh>6$oU{TSsYO`?W$K|MDWQRqsQT)t~V7s=Jr{l4l(% zc}5uSY%Dy86@pdN(6PA*A51o{40lr*>6!12_J_0Wy?!Y$A>k)Sf6zj%ND?2{tJWBq zi>RHT=L1-&14nmH@{e|td@miZU#2>ieBf^e%9quX_TK<{OJ(*>=Sw$3iF79o{4N{e z4c8RjBkox=7NQ>47Ra4%s*{9+%+QuvA7$e_7bT_o%Vi7F)(_B=44&YJ!u!-g!~~dN z*5Q}mQ?|*pL05E0Cl|)~BOF6?25IHCQQy;r`Y$7r0(P*ZSSlp62tROrh* zNb$a7S8{&BJS&NIs^jIL@iT|ztB@D6KGBBe(+rw?+imcMK-ekGr5B_NSuo66*&qap zQ`m2~8rDE-B=ayY*4SnpBhu4S+_`aFhbZWlr|=ew(^}Z!_+0d~dF*Fz*@GfaB0Be- zu$w_?_M*B5sS^G@20Hq5r25hlA5TD)UfVt00I^9a4ixRRGJFK%}HGbe7-H9w}O4lr-E>!kI!Y8~sXa%$n z(%I(lZTr#M=1hl>(o>P}Bi6t=<8K!a%JE<2Q%ZR}=J5~Z4pyQKC6On9rJRA$p{&w1 z#Ez%~JzVBeBakhCAeYM(fvng2Zj7`Bv*l*{<$5c$B!*_wrDguR zP}F<+75nSmOn^HYs@}%^K(l<5mB7FO?fV`B)<&%dotN={z6Id6>^Uwb#^chJkJ zct5{ru}!rl8to8Py>nYha6P&@$_U4!>P*dwX`B0d!GirVp6gbS*xuMIs38F3)lg%8M!Q|W~(R`5NM7D0WNH_>9o3hwCRHfwB!bXQ)i z=2s%eH+oDC_*z<;Sn=3jr2fN@+maN}F7!m6f@Lqz*J`5MDP=3Z(n((F7X;`RAxO2* zDS1{o5+^j)oXan{OC9;@dHV3n2OjHaXgC7q_Q$);EQfC(08)Yy>yj>Vhm4b;yoY5^XBCO9`2h5J}SutIgg-NT|K`i9m811l4ja9QFg zR10@yUxM>xsBY(Gla1;7YfR<&%hHef_$e@^jJaF!*uBW+)oVBc_^h{95(Z^^=tYNV z2k-;`LQxdl@#b7WRSlKR*T%{n0p2OCc9oZ@-fP+v@#@2@ovxBlijG{Z*NK|A?yCt< zqY5}>oj+nanP-;UIy0y4_e`o+ffM8W&$G>%hYp9%62#Gmp$(32k1WlaZ232^d|aa; zF4vMn`58Rz&FpsPbZRd5YH1kTX1y%y|Ip;dSjWyo<%vsCNWl5w3&lailkzZDu%Sv^Hkbx z?UX0O?$Ue->*he^*yc*lKdcHkxkyZaaYUW|B8XCx2>Y$Hy17?kHZHKpCz#|`_fzMpb3PQ z0~uE8fve|@GcOnG)t*s<5Gza>lZi=x2%ew++%NB)mDXh#AdfGaPd845^NHZ%i8|{I z`zD-DN^qh3*fk*CoFMBGg;=_&>=G?yR$-!CioXzwhXK&6((+d{z1isC239(OhA#L5 z)mDbPLr_SuFY>FlJol7HH?)NAFOeZTPO^NT37Dpj$}VAhuQ_C zL>XM!&`%9m7cN&5`x&apliml}_nzYYN04{G1mODIMt( zsw9JwP^;d2XF^`!@vxJ-+E%trzCW3Vu`s0H1wu8}GzB@36`Q|pfBj~CsE?&?G@iW%h-$rk0chq_dbZ>m2VZ-j(oI2)E`f> z7`@{H_2N@87#XOpW23PvQ&?8+`vhB7YASZi6s>OT8k?yRAYwa7B{viaw;xv{^y@I2 zmD$LFPqFBPG78b8K&2H*Qp|WL=Q%_Td}H*Y|B z^wPVKY{CypZ%!rYbS#K^ou5OPdcd|(_Dzd$ZR=Lct<<#c);lCdF*Wq3W#sFV;oA!b zg+xth6iMumM(0ESk z>OyFxrxcIo_^b{n{MA2%pU*~x_LMf3-C`ljxpBu{PcV-fvZr`~R_^o$Of?{99)gjw(V zdkXvQ1=izM)Z{hUSyhtr7wa0Lijd7zPNYzgA$q(`KR8_*mL4huMhZj+08;cL0r%LM zPesde{g$b~bWo#rxBlBLzITE#_H=yP*Se}&)%6aOMQ$`r?jc`D_v1yLVB%yFcy`O~ z=!ZaADz3TGnz}E-gy8|Ck?cC^2%+>Sph%C?o!$1>535;Mhhpol$YwMNM*@@;bXVIh&Eblo^|OP=BTxCiwW)t-en7 zLG*|P?#<#f=H@D_O6xX{nqNa4CYitTlD3U+M&Egedrmze;<5^n5t#`qIq{2+WepGM zRnO@x!f0Xf6qP?AYDRC~$&AujZ_nuIu}mF{x@1{+*)Csza6qsMlZR=4t%GR|yjmQY+{!J0daC(W~zBQSO2iV7a{hVO_W8 zX6|6&{X6+--~A3{*NU?*Wd=ye2C2)sxQKH)3sJ!rp;_y|hF46!s>5A}dr2`jeY&!(nfETB!HPEUypBWjCC zh9e6+1JKzT0Ehm6k*F=Pm=WGZ;F=ZQS*7ri#kaX3Q{?RUBooROBxyXe^mScc+xgIv zCOr2zH@s5HqMYBK@}N2Dc!}Flm1>n~4x|aQ=9<;GZW^5lml?(n+b+vq)IgAL5)A`} z^z41SB>R|0$TnH!KhOhaHvGsi%z_f9Fup}b|B^&wGaH4^lC+4DZjU0m@Oa=1a4J)= zD~P()I{220V*383z>>Bli$WOc$}2XugA$vP!iAemy}wju$hG|^EM&Mhfs=jJc=`-( z8~RzE)%W{7kl#U|iJ5HrkG)Umfv(;#R$CFpH&WG^fk>ka`}~bVmpUUa_gjtkV&`Hm ze0RuFL%6~9U`YOQ9R7-En`J2e@_-xmpCe2=YWRjxTB^&c3bs&U1pSBPvOIsIx6) zHL^koCSo(SO2eJ0j?emQ;uM)^r}kTO6Ksi(n=G$J2AIFyjqfP7OH~E=6GeeLgqDz6 zW_R!I4zAqx%_rKA5Ac|WO=XbbQg}1^c}c>e%*Apy0#|d4stuLKuhn?Ox5x-ItQyhk z@j8IfFx}YvEvcey$WCE>|Fz6-kWy>=l#h(M-oF;JjvSE;IqM-dbBT!;jqe`oSqM5w zSzfX0mvK`Jj{$55UqLPg9tUY)?K9fQCBAF{!lb~*z#gzZs;=^yFqZvkmLtO=W_t^k zx5gN(QSTLKJ$=4lUN@)Z$veC&w#f^A`v=h8z9rNXsO*@S_-7oc<2yao#FD;WQ!m$p zD`}m$5SQ+dn*_Fp^RjlA7#82mJ!bO(wa1RFd$jxTU$ZbYzf&LrPDZVTSARuJvv2{* zb_|mH9B9a(HyAmrEl)}3oa*B=0c0Q?jMRz|lwu=fNhJ?Wk2w!vN{sMA`vDf&Y@H4r zO#{9sI3q7-mSiT!&0x-b5iP|-6bQPQ88|=2|LSsCT?cd|8Z8kgvBo^Gff8+m`AyPv zIKgQe&`a1ys$;Y#SU5-(w5*64qs8&+Af=nqBfI9wSh9o;=(r|$ z(z94-U*EoTH=ah-k0g*9A8^VAh5rUeC^ zhkN~Ro@U7z4%@x^BDcSs0_8PX3xfRR{XTi`SRSc;WSCdaPXW!CUi1zuGu-YJ(t;mm zW;^odO3z(~J@>Y!u1w8{jqLZO&Takv$&05!4Z7F){A(L?eH=n4AS0J18(#%Y0rLSwJr+Uux1Z{e~vy#)ou; zG~Re!`c}_GN8nXf^{ER?mEcTMfboHUa6PYHq2 zq`smK=&-Dh%*64@j-od$3y&=QfEf?zk;AbgPKNT+Hg#gWy-{QzD5v`nXLN_ce)E`e zOfao3Pbg{?g}$%uMMcorGt_*WM~Q@sFO@S8@f^=sM_^KjUG=0FBQoz!0fD!N8R#8@ z+GaFmQEc#d>i*>{1u3C)tMke&kUCAGM}Tvqt3|hEmVz8oWx`Sf7UX(_D#SXhYEpjX zRQkeU<%E^F71_Xjv&5qY&3o5K)6L;(v>_+=HIKlGq3(m4;XtT|G+R>&H^rf7pK{McJ*3pw#d z13Lc*f4&HRwW0uN<{xfvGC)}Ef4@*_EugaQ(b%AbYk%CjWJR_NY(3lBG5lMBlyv{V z@Pd1CgW{w>NcsVu`EBQDixZP)i0u!%{ZC<;a0oHG$=%Ep2q?r|l#q&i;?}nr9tf;~ zuY=~3=0w4(seq`HIbeYJ4{Y9z{)-RO#5w%v{PBgL);S-F2a0$3+d{o{59fs{MDF;k!3sNsS9l|mS-5j_BeE#F)DLD}Rzw9fp zO*6k2rb+jE*ylA3S_ZlkNfj|NyY>|2>L0Yvj6}X^s3VWsXC6P=%+iBVfSsv-;Y~bh z7DBs~iCr*DTtcFeOYi@&uK~}iDB?vBpF&df4NYWMoLX3)`ta}@4grCdrZ_a<+Z$xd zCWTe0P1gqOmj`HfBMc9kyy3A2*uj`C@qUsUgQRg<&3s}_#J+hx#d8>Dd1LSiU$>4n zkJMZuG#JGQI=YPH*_hJi+rbTU{fWMq!f#iaH*!1IUP!q4=d@*3giOy+ufElcpX;pJ4ir*UYF!J@8FSPN104k7J)o> z~OcFG_)uKw6rBlwJ&Xcahys<1TU0Bo?$0u;cX z1y&DHal@iMi(Cj+$e8F9q=r8qDoZzub+>^(s=tpQDYR{KzOi${gp!Pw3*TW^X%0@0 z+t-anl4!+%&wy{(+6|{H!S(86jHD=>(70Q|P9=1J!>o6%HGUnWzs<@$NP? zpw}tq%J@6)(NRRkfhfJT`w56wyUaHa#-6WqCTtrGHxawzXOfkENbYpl3uzdu>{D0oq8yav5K!GD(nr3;wdg_%*8AZnKBx%rBZ@AH^=vzLG~ zV2(`=hIzf=)?T<*CplX?T9`5Y0XFbd?;e&AmL< zMwNL`W zcRknJ;XJ8O?=NX%v>06y;nAz?pj*jXh#V=o>0@O0EAhTjcO$Lgm>39bFV(0}BQNmw zN0M@i6c_umoL>ylV~ieKoL)g5Xf6Jb&=&OhdwrGca|MeYSM8qpcm-XJmt6{#+3U`= zR@kpts-QB#Sux z^AS@S@ z3mLAyn=dPh8SQ~EUbW--hfbcNzTXKM73v5eK7oo=4-c+BBl73vWVT1ZPa}mlnhtmK zPJ+PAO+JP`3{QtP804PTrDCHKn*uv@3XFp32amDhoz|%fq*Zy*99u^9d-`McZ7Ig$ z3ICbMpEij(Q>`~tJ`~M11b#o&ag#0!T@Afr!%uAE!Q+z@7zGHg@ zs&@t9o+;ZTjsLhhFbP~Rm^+a#I8+YhhriPFOH^ZrbG`vH_+cRu){NbVt+1by=!SlB zx#pu3QKVIu0?~x=`#|giH(VRkS4wc!%g% z(68#b<~nDuytLo3#hK$W}G#hmOjBcl#qW!<{2j{0#$vC{(k zLx<(f4nuw39OPY_ zo!<8Vwz+I82&xGO9P=*=K|SIDNryYPKW&&GJuPg?AtW zYw}U?8bhw_ZK&ErvOs>(k6pquA)sfWHh=NaPZaTPl>G6qa3OEzcWz&CS#B7MmJ2@o zrF@B-7LKDyA!w55rmU4dCk5k>733$furtZ90Cj5jrduMXn<{o{RB_3EdqYFF>>-fOSbU9~^EHa&s(G%fih>3DB$!}44G_djNf z@-!zst!kpmZbj-ktfod(fmaB5&yjY0g`!4kCwvxy(*hD+)lJpoGfOOU<|(7HX|0# zCpnN20B>I-Urf;Z=oow%N~_&*S%-Iz9=`Rtz*Vx9VoLd^v>(ZSxW*Lh9Fl8(BuMDF z_T^dqmBMr+oxpSmdbA25DQLlY(kxacnlj>+H=0kWFv~bc5a(D@cEC0c!B8j;IZZs0 z=gOlY9k^W+(gL9~lI6o*EXusAfaD!JFEySRQ5G7aJXoiAQN18q2$1g2Vzk=sHZw@U zD{WqZXZgJ@8zi|P;&4`&=2PP&Pt6r)rW0>6ssr3#vEaxyf!QAufcNXO`dR$8pbPA( z{I2|3YHdt5|L3H&MZM6GImQr!iO~u;Z=p{Wow?{u4fLD2QyzPzao={#q(lTC;$Afa zJ;{Z{%i{DcE&Jt7wpk{YlMM*yajB)qLUO|y*F?8u+fgpgWYLDIDEAvjf{Ce~zy-|M zmK2`H?XBbKZWDt)Ed&v5tKW1q!ym_^Vun`xj`Wi`W*^wf4UB}-DL>)1io|0gl8*pwDG)9H zWgp5{&z9Ww_bidTOI571xfw_#dd$(>?Wq_mjHXdF>%|$cA&)=R?SbgUdFvBzdx>>} z9K+@!1I-$57%CAeyy{qawB~8FYcKW>%Wo@`=-3472TQ8-v(a|M5#k;;TEN;j1{0UX zPHfi&2%X|Z?TSyuC2I%O@ZRliPghmybG3;a7h%paaqq>-qE7g>{|op2!FAE(`fz z=?cU7EG>a}N@3*tYr=_@wbvHmu0Ml@+T0giCdWd)iTl8`tGQ@z4b^Gsv8G8IJl8-D z9ErP88qCAPp`Kpd$Q=dos)n#CBG4Js;{+D4YW@_wLb_jMgRu=?QEN!g>u1zM&co@= z^5X$XC!F+pH|JVA0!B6O)XKOnWN-kA97+%H43iR4@>a`}Q4ho^8!Y-X|CI0C^6Wu(Dw;qf zRWA?uM?FOT_M1MW$1KvKOmE}23)1{#Kk(V;nn}@huO9M+YQsNS^z>}{GZw@eD0)^F z3}9|3Cu%cd08R^WHIyB17IJ}i^Mz}VNUacbjY#sQJ^ckiSp`u;idW4T z=f-G?(gIjfN9v z5>L&B(^ks87iAP7ibbA5isZ8lFp6jqx_UdCNAI5=rtr|^RXTOezl%?p<+y3SeSBr# z)SnsQW$C?4;}oZwo$HBdv6A~*IBjY6thK^i7t`35#d6oQbN#Z%+dt*{%ocQUa`_XH z`OT$nJnYH&4qXj33{29w?0MaJ5mA}qg&=(qWe`ECTl&L3L#iIYXtPk&{&@Dt15Gy< z5;;PO`4lfIyh7r= zZwR;e>hdT!nvE~J8ACyY#ODl{;_{X%pcXwFWg9b9kh$}qUDzz|YFOYdZm2Vnze0Ze zBz#RZXmoKF!K_`e=IhFGYVj#WEI0 z*rCBh26(O$W$~7NOsgQF)L78HbbZ7Ubvjd1Q91l@Atx7mCYF%4vg#|3igO(C16U2B ztG&1P-qAu`$@#sAe$e zNLdSF;dKkXk0rh>r?c^!maYo?U9!B|kq_oiM24^jfEU9~XLSg5(S z^>Q};CVV5FERr@YNjv3iD*c?6JOjXNt8_{3YVcsh^<|v0<5ZZO<`S`3G?4YT;U+C) zkuIi3({|C*<9Lh9UvuX<^sYBXj|Bp*TLZr!+*?b=$MM+p$4WZh7%s8z#lb&dM=iE5V+BSF^*zAnd`etOtvl3LBiReBacjS5fV#nmG-vnI^6m};nmKvdT zLX)DPP64o^(Tf0C`RIHc3q-_mGN8qO%ZZJLG(kR$#L!$o%M6oSUDj4g;ihyoaM^k4 z3fLi*YMrTM6$eW4-I>ai$RvxN%%bnjj8_G;me8|$5;l^YMvP_yV$A>~Kb2hMw+g&m zWoV1yGMW4+{jf^t9Rysq2D&cgSCQ(6XPn;r<(Rge+D7F6PoYv!s{9!D$93el>pa}x5YydJus2| z#`i<~BYIt+%>iwirgBiD{E^N^ko&Z3VJ4eiMlC)F(N`$Cbf}N{^iH!vw1Rqb00Z-~ z4R@wlJNLx-Uh))}5^-rnuA2l(G8gnre9usNIfhYP3CK{QsIaQ2Y{>NSG@Ob7-9y{< zfR=)$N{BVvV1Te&p5O__z*-yh8(mqsnJ=R~8f%}_lJdqL(;zy3_TnT{j=arKcU40d zYEqejzWG5MGPH{5L&Tpzlc6r7MBCFgn~Zh(g)3S>%Mn7M-c=66>0x2ZPQvWeOLsF> zTG`PaT2HaGFhiYY_|BQUOB1Ry>*2GH4;-_z{NS&!1;~>AL5jwM#!{Zlh+A-{@BE*x z7s*e`W}g^n`4XRA@)}cuk6ZxB(WsqVDd8+mmR&)#!TttbV$5h`-4s?cyZY?E(n4ZV=PbCDUo2mujUE z(7GRla}V-%ODli43wxzgerO_Xo9&)C*%Ju88OED|hh3zZ*><|jc}jm+L_FPih29*H z+II!KcxH2#(Wh{3WI8_c3NVjUk;?th$No6*! zlw<}%ny(cp(%c;5*}A6kv6lz1(%4+8-L(5#`LD}Jqii7LqRe({NMX&{@$W`AZpzE@v^QjC4zkd7ebL}e% z@JF9qH2^kgClIP=4kc9rB!)QSuW>}TY*Q>*7(|k)SnhTy%N+1XS2%=y&J0LMzm28l zzgg6&<%S-F57y~!l~d0T&|RTC4u$HP8JOp^h$4jpYUPk3VO$Cs!~jhWhu*q2HQ_=2 z4xN$Iw z5sL%pT#*zA`PTrC?vY6Nd^rY-81&RVC6Ts)5tY%i#u4^aeu*f>{RRnW+0`fez;pcA z%`v#$(R5?g4!^$jvE)69aU1!%bHegO&rXt?$~1H!WDzZderAS>Wc0F70{lNQhzu=1 z6Byk(&9)` zWU6;R5XIBPpdl3ynPY!n787q!ZM+M;xgi9{?g0?WL(ykgM382GOE?kxP-qHS5DFme z_`>sO1Oz@P_BjZdg$P1ikbQ+8pkMqF41jvqPO~#OoWkDe`2kyOhu#1M*+9k5PdJjJ zt>>!@(Gbq(?*f#g?W+vp>*9I6zTPKO$1K)l-WQgt#;gy4WD-noE&yt#j3n`+>zz>v zLG_RJq7P)(jc>m>O0Pr_*|G#rTgsAu(%=N8k1Z=I0q?P^?^@I7Z#Q#eD_*?PsD*Y?CAHed( z4)}kC_G>3>hON`X2VT7+w{7T9M-hvz@zGOjLbe{mNxyz;VulSGVu^B${o;OLJEMKE z&DpasvS-Z{x-xmPhxC~bqK!|d>e77B=Lwrnx||F&TA>Z~GsOj)4|QkSESb;>Nwe{q zm8*(tvnQ_A&=o%x64u+eCgv^EG@cvGx08v->2)UNubP<>ZoRWUN0f&a&%@eS#64&p z8&kWPRT)SWvSr1y;m-Ed>v;%$xJ?*N#h9pRoE78NH=ODKd%|IRGQ9WK;XbDVLwDPT zKlN1Be+I03NPgd}5&;E=a0-nJ24Ml0Kp~#tl<*YGnQr_}JmV=MET%`2po8z1TkPZu z5|pF>3<13=1O)+|B#%Qpb8y2=Oz#v@=OMAiyawwjVYc$iPlX1P6xdQ7AT<;g`gP1e z*4JG4Izs-`q_^*`{zNKR1-gw|;r77NOR08n^Bp3>sk(kXXk>(yLk9ki`f3Kg^(X+_ zE1p3TJsG$kYJ)Q(9Qy8no^}pgB$ohOWfjfqj9I}IWm?BJ`~#opsN{j=EB)92ryvXY zm3@{`0bTSh@uPa*88@A;L>t)WNSmTlH7;YY+Li>%abgbz%J5 z<)13HFYL%y6WiZ2{A)yQe5Nn@2qQl0-!lWgI{qDpg@yG??n?*Fe}?);=1cZ(llEUS z$5;8=1;G5b{B32X|EGMlGk?KP82{0Y8G!#){!!y=pnucrOXzRK-%T6;f3i&e-j)CU zK>sf+lmGe*|BYnwMFRN?5cwC$gyo9`@fxee`t&|A94O{!dsF0PB~d`VXwhKPUf3t$(;Ckbu9G zrT^sX{#WeMzjI9h04BCCkNYpz$M|IqUpoB1oL@$VyB@M~6R*!zWo5a?cCyvx$vF&h zjEn$&nn5_9Q9Lt!tnWE3e1fo2NSGDlcUs>NMAjZ-0DO{f-f_$8VQ4-f2L~e)`RT|= zI=>LfV)t|WnR4?(+vA4g^bPwFYHk2LmN~)qSB*1$dI6GFS3S^L&{;<~yeve(2 zRp**^DC7!_SzRHU#VXR`eNU35w0l2q47l51wY`jFEtD7;4&VaBWM{MfeBsg;I&fSO z_1@$a0~ar9J;zTq0P6KrB%`GsI$wbBiAp}#?4Y6Q{LTK>oX#c5QzQ(KSUZ^}7Mq%- zzYq_CkA3?w#wE)!3Opb>6cf21cq8T;K_XZZVu@%(<4xX3e-G0du_1{oc~R_IkTvw| zybt35%luR?CXRv;bv7J#vX8YRITy1gKy5@)8`rf@iHWZ`BLDX8%dsJPNk%uTsd)59 z1+Nu&x}4x$tb2i~^tK?N0%cipc%jn>*xhfe)KZ~doMn{6QjO3Va1Tm>53_H7Lw%Z% z6Fk^UEK)_{8aoM~?(nMwA3++(VOb)Fj?o!P4~nth2dfLIAdroBEeVcFh!QVWY%Y*N zOm!1SI0dA82~r)?KNOfrYG=J9ND+dMXqwS|a#G76x{Ad|tS$7Hs)lBt7h`(Ib`jpk zU7mDs-h_AH9y9HrW~zR}&!wopHzo2;Iv#w-NQd6Gvpkz7^S z0iFY{v=*{Pi?J?%;dY4W;DhyJ`AIlDn!F&&bS%e_Xp{44#KIEUNybHdj=+Ol`h;qrZ8Ti^&Q)LR4H|Gq~7g{$OG|fCcV}Q zeg~mS$ort>Aa6*v1XV$MB1M?JPrH5gHRs;@!pFkvZyfL}UA|Mk+4Wu&*|_9UaJLt- zea9QsDPA`pU5;3t$mR_()!EmRtK+jf?-v>k@riD&rDT`^&7->KoOzsc9v9xN58RKa z8rjvO^Q&`kenf{riClFzB{L+B0HQwCJV|PJ&k*iGgk2$qz^oGzZGy-o7l~=HYKh_l zO!qV{*$<6YL3Nww=&86VUaWx>;|7e2-`Y@Y<`Cr9Kir|cy1~8wz>>dYmt5OBfOW#> zGH8T&sLpd}$Og||-?2Jzboy5nJkCwt5xk&#!_x3!rF^#_QR8Jvlh&VKb!duvPrB&c z=6_(}9E++|EOD0$fr7{_rWZ;fmqN7R!?Hn4f*j{xh3T8`^Xvof6W_7iLH3{_HwlLt zAldcbg|&}<>T)V{;{L#m*GYnvyplXByk(3hawyWGoQ`!J`>AYHsDM#gp~9o|VVo+; zWDa403wovdR!cvm$KsjxJYal>3~MSM%Qkxh@}dtA8SECT)XUMue&aelG}WISw@r3N zV2s-o(#1!)bFPyz`Cd@smT_K`UAwn?wQ9Uiv?kQQ3%dh$f`dvl&4*cON>_(w_OmW{ zvBuu2obNcBitt(|iwz`lt&h(gKEqF_${Fw2E0P08niGxDCM?|Pnq2RIkFU*+r zLG%67aOo#JRx{@32rok}!nHw7f^3cvk-OvieW(}Y=TwQ(EBs9mv3!acM$6tSR|ML7 zOopi>eXbw^{;K$GBF_@&P+byTT3UkQQQ3X06MVKZ-hvZx!=|T@r)DMPGENb65scMP zd)9C*KZIM7mVNk#=Pg_MIp&c#9rQ(_Q6a|24Y1N?g2kk@v^FMZM+;haw8syr_StN6 zDiI;@%de#%JGMB_0b7ywBllCE(b)`<*reNY@RP-z^tdwOP=)t%^K-Z-BqwkVvT~m6 z?mw{KRqg77P_+cN$hPC~NfhpZ6x-9D?5HbROw zfKIIk-FO0O?J*s8S@%gCA{a4+!mD~;Y20MEkGWDe#G3NKuL>^M7-1CJ1Kw~hTxPb; zNjqeR<5>hOa6~eBh;;qdDC%`K@zKEM?A?~4jkr>SSe4>pozOh=@JfS>aLC`lk26KJ z3wS3BWq)^(M#zDW5-!edlM`)5?38XkjFi7Y>!^x9fFHdyUrlxx(Oaage-tccHl1UPh?C zRc@+o9(}oqNHQoK2>R@Py`csmQHyK@t|wC~RMHxxCyU{Oql0cdDv-+Tt+uvt2<}w^X+yt}Tzb zpvbfNILEEw0uFd@7M8<)0mSOlMX!Tk&1Gx@*TklpwfSNtYS2#5NzQDk#|+Cl4A!Dt zlg)B6r^s)!eO6n=n$N4psnV0RPO{!$KCH4y&ufbovm+x7i<+%unpT9`1yD~ysx0jv>D5J#q?T1Dk+410e!Epo=Et$C)~CKj%vC&ZY-wWa1zxILZHM6B zVMDTCE|*1%oN!`qwRz!v;%-NURV?!rX|0+`5Uoi_GK3R=jujWK9UQy!Jevb?Dj0dD zQG|hRVqhM_@fSBdCOlxat@|mc9 zm^)(U=V#40TeG-@O~E&!$KKPV7Nnp4Me&pAdY5D+RnHRX_L}Zi!qDl5p2Y39$fd^C zG_c}j7!B$rW}}a7PY&d95?1E|ncMb>-R)-Q#=J_|yz;CQjpqCJ#gA$WQ%$0UXukpd zdqdJH_5Al!EgYlOG-l^(v(2<3Wa*>O{&W4{s9Kb7d6S3)4Zy38n(S%Q&_|iCxa|2`kz{6(CAFL;WMO2e`-OB@X>rWn)k-} zH{5{FN%062eyA*RJteNF>TjD_{-|{!8rT9KH+Tw$eM-Sh3jA|>i9g^!_=-r-m8o~q z`~zao%Z)opMSxHjA?}%SqV*!c$QDF<=i=ZH0kOFk)S9s1w~7FyDZIjhfY`N|fP|g( z_vzE1!eK7~347gDC2BCQDKrR*>=0u-0u*CA^XreY!hpN3w3yTBAEl+eH<$#CjHq0i z!!F&dKQL+%WBASHbB2GE3UZ!a_T0z{RQ0s>e&QP)Qwp5I6(>_=6B(@_sYM|%?t%%3 zJdo{EE;u{kBoPHf^tKOm2#vISr*_+F$^X-My>+cAwT1-ov*s9i?k8M+P=!;C#-=3k zEPO~&2pEZeI5mcpTP!L(N|aPc0Af`=b0%}Ywy8ys^HPk4Rr{3TVmhzbZRfmOzUb}61>w)kgUR4?i%f%i!COAAUO=dp}&aa;eh zxa;%b{;#n`aea@22Ohj5$j*9G)7r5St1IYkIhwl%C9r_Jjl4_c&4AQ{^}vfcnKPN! z^g#z>J(Cz^4-e6@`oO%myj{v*_%pr7M!e8DGtpDKb_;2NDVrthPH>v>eD~=<74-^k zJHIL)c?n794BCU@a1WTY;odV z4<6E9t9u3E$tcue*u>Rs$U)}~7m9e&5zLqaS91=by1M!n6}%E8xK5VioNbAX!v{vS`lZoPbYTUSku`rYS z35}FSE=%vFS!zjC4k@^&w^8cDI`(=tONJ?Jj;HX)Lb4cQoby>15$h>f4w>kq6$$H! zqP-UDc2$LF(C_rN6ruZU(XFVpb`RjyMX$%uSGk92Y5FFQ#A&j=??{o8%r>&j&dFq$ zx#6%e6$?9?mlis~n=!jF7bBxpKPsu zb8GWj^by3xa>y{&8Wl!8UT(JgvE2H&4pVX~Cn_hRh;QXf_!#59EMLW=s3)Mzx^&Gf zzHCZ{^2}qAPSm062A3X0%1&uBRBW($3j0b7(LE~O%eGq#M|XO>WN0#W`y8zwRheh6 zT}_CE{16!`lnH~qivtF<%U0`FRL;4~6ekuh;zYflYL*#iv=hw0Vl#iV(j58SwoX;|~rSZB-V>H#|R*TqBI zK*@-ZOtKCewGCZE^D3-AoHfvLtugzVL(>6Q_N_xyf}<8Y%XdPuxF6V1w0Xl~E&GJA zbI|PP3#mhjm#1?gBhXU`UW2abvhq`Mi`rI$>`+D^P_ZJmUE)mf{t0D0!YXYUiT-^) z*@!)1e09+Iw6AaSduWs0WpzBJAG;8OVAD02nHbBTABT@N>Ce4$(nG30z2=`%w@}iZ z`)}MGZSH?kOyKf5^)(dPxLT(-ytOG;Z=_uBYFDEdcY|oN3aSqK<(J*@ItNpZB-Ow- zgy2FU1wEn$dP&w>+u!{8o0{QpEuopG2HUYHom_)2x8B4b*^rt}+bl+-vJ>P>mCQmbg~_JwmD-c`sJS zpq8q-Y%E$EoES(U5dy(qh()FJQs-dIHlMg%V;^>Eh%rXbBDR{xmESSWqpw>ZKPEe& zF%p)LJ7!>kX3`MqV8{*;i{EG&=6{!#Hz`% z%CQi=l3g&p5N$Q%PfkBXv7P z%$H>2o9A_Piz+Y%wg4F>+)R7KV}572bRMnuY$Q z%OeaE?Ca_2>$z8eX1!!efgo9wfD!^diJrku{3%;Sw_#!2Ld(^qC*o$(8}`ey|53Q9 zzsH^FMkPo(>Ego|8y0zN@FVCCVbd?`=rc@o$9Phn+GoLS)$b9BUF8q zDT%2`&g|ivQz^lsi5)4OB;nS?`J{Wrq?E+P$Rs2j(bnhjk(HFJld8xQ4B85TSmKGL z^oOvp$tq|63bSGM`T_I$0hG-rvsv$@^DNLLgT|4q7{hfFLw1(5I{EZVUle4F*n&|~ z`ay2vgt?pDm3{noLNQSgGVmj?K)n_(7Y`$TFurF%wgvX31sBv8y(#mz5)UW=kbqzg z=@fue#;t`#qCnlm)g0B@hRxaU=grrb`nov7o1%l-At7KlG9Y^@wi%|ADuT$#h8ci9 zuum`Ak0D4%C`dYurW*e_qJ&?!^GlWHn6oFP19;n>wfQ<<6F*&zZOHn6kRoNVF&}3- zt998OY~Im5oDZ7am65=7*h@ByjelO$9()8XzD7nhx@xDYqcklnUg$7iH{Yi|&rZnu zzhM5nyMshoy2Dv6&!M#CC^5L9g7H7QapFCb2B`GB{<1_tz@3-~Jh2B<#4o`5_w4us zStUN`d@~ea#QxSyWz>aBr#wEuXkNe}Yc0QA9wtSS1XrU-fXa*>5i;K5lE3LJtpXn> zkP?c}b%u&fgdl05f0jDZmo#NMPZu!S5`NN&Wf7EV9v~aEm~#Rwo5E4SBt0b(auSw; zSMd?ZAoW(spyhSH(W*;3Y^eIH|4OtnBZP~`%hyfe=q_vNv@C*(F~u~4O9l%K9nE6o zO+J=tpzEP)D=dQFptEy}lYGzTfM2)DU&7`zl~vc_QFD z`ubp75j_!rQ*qZ|ErAAp z{#%HC4l?pfXBU3+=bY6yyz8jVYD|$MKk;@aS%;CyNLSiycJDx^n%legA@M!3y@u

? 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

=ma$FN6pB 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 List[dict]: + """ + Extracts URI annotations from a single or a list of PDF object references on a specific page. + The type of annots (list or not) depends on the pdf formatting. The function detectes the type + of annots and then pass on to get_uris_from_annots function as a List. + + Args: + annots (Union[PDFObjRef, List[PDFObjRef]]): A single or a list of PDF object references + representing annotations on the page. + height (float): The height of the page in the specified coordinate system. + coordinate_system (Union[PixelSpace, PointSpace]): The coordinate system used to represent + the annotations' coordinates. + page_number (int): The page number from which to extract annotations. + + Returns: + List[dict]: A list of dictionaries, each containing information about a URI annotation, + including its coordinates, bounding box, type, URI link, and page number. + """ if isinstance(annots, List): return get_uris_from_annots(annots, height, coordinate_system, page_number) return get_uris_from_annots(annots.resolve(), height, coordinate_system, page_number) @@ -879,6 +896,21 @@ def get_uris_from_annots( coordinate_system: Union[PixelSpace, PointSpace], page_number: int, ) -> List[dict]: + """ + Extracts URI annotations from a list of PDF object references. + + Args: + annots (List[PDFObjRef]): A list of PDF object references representing annotations on + a page. + height (Union[int, float]): The height of the page in the specified coordinate system. + coordinate_system (Union[PixelSpace, PointSpace]): The coordinate system used to represent + the annotations' coordinates. + page_number (int): The page number from which to extract annotations. + + Returns: + List[dict]: A list of dictionaries, each containing information about a URI annotation, + including its coordinates, bounding box, type, URI link, and page number. + """ annotation_list = [] for annotation in annots: annotation_dict = try_resolve(annotation) @@ -916,6 +948,10 @@ def get_uris_from_annots( def try_resolve(annot: PDFObjRef): + """ + Attempt to resolve a PDF object reference. If successful, returns the resolved object; + otherwise, returns the original reference. + """ try: return annot.resolve() except Exception: @@ -926,6 +962,19 @@ def rect_to_bbox( rect: Tuple[float, float, float, float], height: float, ) -> Tuple[float, float, float, float]: + """ + Converts a PDF rectangle coordinates (x1, y1, x2, y2) to a bounding box in the specified + coordinate system where the vertical axis is measured from the top of the page. + + Args: + rect (Tuple[float, float, float, float]): A tuple representing a PDF rectangle + coordinates (x1, y1, x2, y2). + height (float): The height of the page in the specified coordinate system. + + Returns: + Tuple[float, float, float, float]: A tuple representing the bounding box coordinates + (x1, y1, x2, y2) with the y-coordinates adjusted to be measured from the top of the page. + """ x1, y2, x2, y1 = rect y1 = height - y1 y2 = height - y2 @@ -936,6 +985,19 @@ def calculate_intersection_area( bbox1: Tuple[float, float, float, float], bbox2: Tuple[float, float, float, float], ) -> float: + """ + Calculate the area of intersection between two bounding boxes. + + Args: + bbox1 (Tuple[float, float, float, float]): The coordinates of the first bounding box + in the format (x1, y1, x2, y2). + bbox2 (Tuple[float, float, float, float]): The coordinates of the second bounding box + in the format (x1, y1, x2, y2). + + Returns: + float: The area of intersection between the two bounding boxes. If there is no + intersection, the function returns 0.0. + """ x1_1, y1_1, x2_1, y2_1 = bbox1 x1_2, y1_2, x2_2, y2_2 = bbox2 @@ -954,6 +1016,16 @@ def calculate_intersection_area( def calculate_bbox_area(bbox: Tuple[float, float, float, float]) -> float: + """ + Calculate the area of a bounding box. + + Args: + bbox (Tuple[float, float, float, float]): The coordinates of the bounding box + in the format (x1, y1, x2, y2). + + Returns: + float: The area of the bounding box, computed as the product of its width and height. + """ x1, y1, x2, y2 = bbox area = (x2 - x1) * (y2 - y1) return area @@ -965,6 +1037,24 @@ def check_annotations_within_element( page_number: int, threshold: float = 0.9, ) -> List[dict]: + """ + Filter annotations that are within or highly overlap with a specified element on a page. + + Args: + annotation_list (List[dict]): A list of dictionaries, each containing information + about an annotation. + element_bbox (Tuple[float, float, float, float]): The bounding box coordinates of the + specified element in the bbox format (x1, y1, x2, y2). + page_number (int): The page number to which the annotations and element belong. + threshold (float, optional): The threshold value (between 0.0 and 1.0) that determines + the minimum overlap required for an annotation to be considered within the element. + Default is 0.9. + + Returns: + List[dict]: A list of dictionaries containing information about annotations that are + within or highly overlap with the specified element on the given page, based on the + specified threshold. + """ annotations_within_element = [] for annotation in annotation_list: if annotation["page_number"] == page_number and ( @@ -980,6 +1070,19 @@ def get_word_bounding_box_from_element( obj: LTTextBox, height: float, ) -> Tuple[List[LTChar], List[dict]]: + """ + Extracts characters and word bounding boxes from a PDF text element. + + Args: + obj (LTTextBox): The PDF text element from which to extract characters and words. + height (float): The height of the page in the specified coordinate system. + + Returns: + Tuple[List[LTChar], List[dict]]: A tuple containing two lists: + - List[LTChar]: A list of LTChar objects representing individual characters. + - List[dict]: A list of dictionaries, each containing information about a word, + including its text, bounding box, and start index in the element's text. + """ characters = [] words = [] text_len = 0 @@ -1002,10 +1105,9 @@ def get_word_bounding_box_from_element( # TODO(klaijan) - isalnum() only works with A-Z, a-z and 0-9 # will need to switch to some pattern matching once we support more languages - if index == 0: + if not word: isalnum = char.isalnum() - - if char.isalnum() != isalnum: + if word and char.isalnum() != isalnum: isalnum = char.isalnum() words.append( {"text": word, "bbox": (x1, y1, x2, y2), "start_index": start_index}, @@ -1028,6 +1130,19 @@ def get_word_bounding_box_from_element( def map_bbox_and_index(words: List[dict], annot: dict): + """ + Maps a bounding box annotation to the corresponding text and start index within a list of words. + + Args: + words (List[dict]): A list of dictionaries, each containing information about a word, + including its text, bounding box, and start index. + annot (dict): The annotation dictionary to be mapped, which will be updated with "text" and + "start_index" fields. + + Returns: + dict: The updated annotation dictionary with "text" representing the mapped text and + "start_index" representing the start index of the mapped text in the list of words. + """ if len(words) == 0: annot["text"] = "" annot["start_index"] = -1 @@ -1059,6 +1174,16 @@ def map_bbox_and_index(words: List[dict], annot: dict): def try_argmin(array: np.ndarray) -> int: + """ + Attempt to find the index of the minimum value in a NumPy array. + + Args: + array (np.ndarray): The NumPy array in which to find the minimum value's index. + + Returns: + int: The index of the minimum value in the array. If the array is empty or an + IndexError occurs, it returns -1. + """ try: return int(np.argmin(array)) except IndexError: From bcd0eee7536765168f82e0f25adf32345757ad2b Mon Sep 17 00:00:00 2001 From: Newel H <37004249+newelh@users.noreply.github.com> Date: Tue, 3 Oct 2023 11:54:36 -0400 Subject: [PATCH 2/5] Feat: Detect all text in HTML Heading tags as titles (#1556) ## Summary This will increase the accuracy of hierarchies in HTML documents and provide more accurate element categorization. If text is in an HTML heading tag and is not a list item, address categorize it as a title. ## Testing ``` from unstructured.partition.html import partition_html elements = partition_html(url="https://www.eda.gov/grants/2015") ``` Before, the date headers at the given url would not be correctly parsed as titles, after this change they are now correctly identified. A unit test to verify the functionality has been added: `test_html_partition::test_html_heading_title_detection` that includes values that were previously detected as narrative text and uncategorized text --- CHANGELOG.md | 13 ++-------- .../partition/test_html_partition.py | 24 ++++++++++++++++++- unstructured/documents/html.py | 7 +++++- 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a37fea2809..febb223d00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,24 +3,15 @@ ### Enhancements * **bump `unstructured-inference` to `0.6.6`** The updated version of `unstructured-inference` makes table extraction in `hi_res` mode configurable to fine tune table extraction performance; it also improves element detection by adding a deduplication post processing step in the `hi_res` partitioning of pdfs and images. +* **Detect text in HTML Heading Tags as Titles** This will increase the accuracy of hierarchies in HTML documents and provide more accurate element categorization. If text is in an HTML heading tag and is not a list item, address, or narrative text, categorize it as a title. * **Update python-based docs** Refactor docs to use the actual unstructured code rather than using the subprocess library to run the cli command itself. +* * **Adds data source properties to SharePoint, Outlook, Onedrive, Reddit, and Slack connectors** These properties (date_created, date_modified, version, source_url, record_locator) are written to element metadata during ingest, mapping elements to information about the document source from which they derive. This functionality enables downstream applications to reveal source document applications, e.g. a link to a GDrive doc, Salesforce record, etc. ### Features ### Fixes * **Fixes partition_pdf is_alnum reference bug** Problem: The `partition_pdf` when attempt to get bounding box from element experienced a reference before assignment error when the first object is not text extractable. Fix: Switched to a flag when the condition is met. Importance: Crucial to be able to partition with pdf. - -## 0.10.17-dev3 - -### Enhancements - -* **Adds data source properties to SharePoint, Outlook, Onedrive, Reddit, and Slack connectors** These properties (date_created, date_modified, version, source_url, record_locator) are written to element metadata during ingest, mapping elements to information about the document source from which they derive. This functionality enables downstream applications to reveal source document applications, e.g. a link to a GDrive doc, Salesforce record, etc. - -### Features - -### Fixes - * **Fix various cases of HTML text missing after partition** Problem: Under certain circumstances, text immediately after some HTML tags will be misssing from partition result. Fix: Updated code to deal with these cases. diff --git a/test_unstructured/partition/test_html_partition.py b/test_unstructured/partition/test_html_partition.py index 48934f73f4..82976a2621 100644 --- a/test_unstructured/partition/test_html_partition.py +++ b/test_unstructured/partition/test_html_partition.py @@ -8,7 +8,7 @@ from unstructured.chunking.title import chunk_by_title from unstructured.cleaners.core import clean_extra_whitespace -from unstructured.documents.elements import ListItem, NarrativeText, Table, Title +from unstructured.documents.elements import EmailAddress, ListItem, NarrativeText, Table, Title from unstructured.documents.html import HTMLTitle from unstructured.partition.html import partition_html from unstructured.partition.json import partition_json @@ -645,3 +645,25 @@ def test_add_chunking_strategy_on_partition_html( chunks = chunk_by_title(elements) assert chunk_elements != elements assert chunk_elements == chunks + + +def test_html_heading_title_detection(): + html_text = """ +

This is a section of narrative text, it's long, flows and has meaning

+

This is a section of narrative text, it's long, flows and has meaning

+

A heading that is at the second level

+

Finally, the third heading

+

December 1-17, 2017

+

email@example.com

+

  • - bulleted item
  • + """ + elements = partition_html(text=html_text) + assert elements == [ + NarrativeText("This is a section of narrative text, it's long, flows and has meaning"), + Title("This is a section of narrative text, it's long, flows and has meaning"), + Title("A heading that is at the second level"), + Title("Finally, the third heading"), + Title("December 1-17, 2017"), + EmailAddress("email@example.com"), + ListItem("- bulleted item"), + ] diff --git a/unstructured/documents/html.py b/unstructured/documents/html.py index 1fbbcbcdfa..77afae1e4a 100644 --- a/unstructured/documents/html.py +++ b/unstructured/documents/html.py @@ -389,7 +389,7 @@ def _text_to_element( links=links, emphasized_texts=emphasized_texts, ) - elif is_possible_title(text): + elif is_heading_tag(tag) or is_possible_title(text): return HTMLTitle( text, tag=tag, @@ -431,6 +431,11 @@ def is_narrative_tag(text: str, tag: str) -> bool: return tag not in HEADING_TAGS and is_possible_narrative_text(text) +def is_heading_tag(tag: str) -> bool: + """Uses tag information to infer whether text is a heading.""" + return tag in HEADING_TAGS + + def _construct_text(tag_elem: etree.Element, include_tail_text: bool = True) -> str: """Extracts text from a text tag element.""" text = "" From 1fb464235a2d4fb399244db4ff2ee7a34202b2d6 Mon Sep 17 00:00:00 2001 From: Amanda Cameron Date: Tue, 3 Oct 2023 09:40:34 -0700 Subject: [PATCH 3/5] chore: Table chunking (#1540) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change is adding to our `add_chunking_strategy` logic so that we are able to chunk Table elements' `text` and `text_as_html` params. In order to keep the functionality under the same `by_title` chunking strategy we have renamed the `combine_under_n_chars` to `max_characters`. It functions the same way for the combining elements under Title's, as well as specifying a chunk size (in chars) for TableChunk elements. *renaming the variable to `max_characters` will also reflect the 'hard max' we will implement for large elements in followup PRs Additionally -> some lint changes snuck in when I ran `make tidy` hence the minor changes in unrelated files :) TODO: ✅ add unit tests --> note: added where I could to unit tests! Some unit tests I just clarified that the chunking strategy was now 'by_title' because we don't have a file example that has Table elements to test the 'by_num_characters' chunking strategy ✅ update changelog To manually test: ``` In [1]: filename="example-docs/example-10k.html" In [2]: from unstructured.chunking.title import chunk_table_element In [3]: from unstructured.partition.auto import partition In [4]: elements = partition(filename) # element at -2 happens to be a Table, and we'll get chunks of char size 4 here In [5]: chunks = chunk_table_element(elements[-2], 4) # examine text and text_as_html params ln [6]: for c in chunks: print(c.text) print(c.metadata.text_as_html) ``` --------- Co-authored-by: Yao You --- CHANGELOG.md | 4 +- test_unstructured/chunking/test_title.py | 51 +++++-- test_unstructured/partition/csv/test_csv.py | 16 ++ test_unstructured/partition/docx/test_docx.py | 43 +++++- test_unstructured/partition/epub/test_epub.py | 21 +++ .../partition/markdown/test_md.py | 2 +- test_unstructured/partition/msg/test_msg.py | 2 +- test_unstructured/partition/odt/test_odt.py | 23 ++- .../partition/pdf-image/test_image.py | 19 +++ .../partition/pdf-image/test_pdf.py | 2 +- test_unstructured/partition/pptx/test_ppt.py | 2 +- test_unstructured/partition/pptx/test_pptx.py | 4 +- .../partition/pypandoc/test_org.py | 2 +- test_unstructured/partition/test_auto.py | 87 ++++++++++- unstructured/__version__.py | 2 +- unstructured/chunking/title.py | 142 +++++++++++------- unstructured/documents/elements.py | 12 ++ unstructured/ingest/interfaces.py | 8 +- unstructured/partition/csv.py | 2 + unstructured/partition/xlsx.py | 2 + unstructured/staging/weaviate.py | 1 + 21 files changed, 356 insertions(+), 91 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index febb223d00..0d06dc15f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 0.10.19-dev6 +## 0.10.19-dev7 ### Enhancements @@ -6,6 +6,8 @@ * **Detect text in HTML Heading Tags as Titles** This will increase the accuracy of hierarchies in HTML documents and provide more accurate element categorization. If text is in an HTML heading tag and is not a list item, address, or narrative text, categorize it as a title. * **Update python-based docs** Refactor docs to use the actual unstructured code rather than using the subprocess library to run the cli command itself. * * **Adds data source properties to SharePoint, Outlook, Onedrive, Reddit, and Slack connectors** These properties (date_created, date_modified, version, source_url, record_locator) are written to element metadata during ingest, mapping elements to information about the document source from which they derive. This functionality enables downstream applications to reveal source document applications, e.g. a link to a GDrive doc, Salesforce record, etc. +* **Adds Table support for the `add_chunking_strategy` decorator to partition functions.** In addition to combining elements under Title elements, user's can now specify the `max_characters=` argument to chunk Table elements into TableChunk elements with `text` and `text_as_html` of length characters. This means partitioned Table results are ready for use in downstream applications without any post processing. + ### Features diff --git a/test_unstructured/chunking/test_title.py b/test_unstructured/chunking/test_title.py index 8ccfde5af8..bc8bdcc6b0 100644 --- a/test_unstructured/chunking/test_title.py +++ b/test_unstructured/chunking/test_title.py @@ -31,7 +31,7 @@ def test_split_elements_by_title_and_table(): Text("It is storming outside."), CheckBox(), ] - sections = _split_elements_by_title_and_table(elements, combine_under_n_chars=0) + sections = _split_elements_by_title_and_table(elements, combine_text_under_n_chars=0) assert sections == [ [ @@ -75,7 +75,7 @@ def test_chunk_by_title(): Text("It is storming outside."), CheckBox(), ] - chunks = chunk_by_title(elements, combine_under_n_chars=0) + chunks = chunk_by_title(elements, combine_text_under_n_chars=0) assert chunks == [ CompositeElement( @@ -112,7 +112,7 @@ def test_chunk_by_title_respects_section_change(): Text("It is storming outside."), CheckBox(), ] - chunks = chunk_by_title(elements, combine_under_n_chars=0) + chunks = chunk_by_title(elements, combine_text_under_n_chars=0) assert chunks == [ CompositeElement( @@ -147,7 +147,7 @@ def test_chunk_by_title_separates_by_page_number(): Text("It is storming outside."), CheckBox(), ] - chunks = chunk_by_title(elements, multipage_sections=False, combine_under_n_chars=0) + chunks = chunk_by_title(elements, multipage_sections=False, combine_text_under_n_chars=0) assert chunks == [ CompositeElement( @@ -182,7 +182,7 @@ def test_chunk_by_title_groups_across_pages(): Text("It is storming outside."), CheckBox(), ] - chunks = chunk_by_title(elements, multipage_sections=True, combine_under_n_chars=0) + chunks = chunk_by_title(elements, multipage_sections=True, combine_text_under_n_chars=0) assert chunks == [ CompositeElement( @@ -212,24 +212,32 @@ def test_add_chunking_strategy_on_partition_html_respects_multipage(): filename, chunking_strategy="by_title", multipage_sections=False, - combine_under_n_chars=0, + combine_text_under_n_chars=0, + new_after_n_chars=300, + max_characters=400, ) partitioned_elements_multipage_true_combine_chars_0 = partition_html( filename, chunking_strategy="by_title", multipage_sections=True, - combine_under_n_chars=0, + combine_text_under_n_chars=0, + new_after_n_chars=300, + max_characters=400, ) elements = partition_html(filename) cleaned_elements_multipage_false_combine_chars_0 = chunk_by_title( elements, multipage_sections=False, - combine_under_n_chars=0, + combine_text_under_n_chars=0, + new_after_n_chars=300, + max_characters=400, ) cleaned_elements_multipage_true_combine_chars_0 = chunk_by_title( elements, multipage_sections=True, - combine_under_n_chars=0, + combine_text_under_n_chars=0, + new_after_n_chars=300, + max_characters=400, ) assert ( partitioned_elements_multipage_false_combine_chars_0 @@ -244,7 +252,21 @@ def test_add_chunking_strategy_on_partition_html_respects_multipage(): ) -def test_add_chunking_strategy_raises_error_for_invalid_n_chars(): +@pytest.mark.parametrize( + ("combine_text_under_n_chars", "new_after_n_chars", "max_characters"), + [ + (-1, -1, -1), + (0, 0, 0), + (-5666, -6777, -8999), + (-5, 40, 50), + (50, 100, 20), + ], +) +def test_add_chunking_strategy_raises_error_for_invalid_n_chars( + combine_text_under_n_chars, + new_after_n_chars, + max_characters, +): elements = [ Title("A Great Day"), Text("Today is a great day."), @@ -258,7 +280,12 @@ def test_add_chunking_strategy_raises_error_for_invalid_n_chars(): CheckBox(), ] with pytest.raises(ValueError): - chunk_by_title(elements, combine_under_n_chars=1, new_after_n_chars=0) + chunk_by_title( + elements, + combine_text_under_n_chars=combine_text_under_n_chars, + new_after_n_chars=new_after_n_chars, + max_characters=max_characters, + ) def test_chunk_by_title_drops_extra_metadata(): @@ -335,7 +362,7 @@ def test_chunk_by_title_drops_extra_metadata(): ), ] - chunks = chunk_by_title(elements, combine_under_n_chars=0) + chunks = chunk_by_title(elements, combine_text_under_n_chars=0) assert str(chunks[0]) == str( CompositeElement("A Great Day\n\nToday is a great day.\n\nIt is sunny outside."), diff --git a/test_unstructured/partition/csv/test_csv.py b/test_unstructured/partition/csv/test_csv.py index 050c2c2567..3f3d5e4ae0 100644 --- a/test_unstructured/partition/csv/test_csv.py +++ b/test_unstructured/partition/csv/test_csv.py @@ -8,6 +8,7 @@ EXPECTED_TEXT, EXPECTED_TEXT_WITH_EMOJI, ) +from unstructured.chunking.title import chunk_by_title from unstructured.cleaners.core import clean_extra_whitespace from unstructured.documents.elements import Table from unstructured.partition.csv import partition_csv @@ -189,3 +190,18 @@ def test_partition_csv_with_json(filename, expected_text, expected_table): assert elements[0].metadata.filename == test_elements[0].metadata.filename for i in range(len(elements)): assert elements[i] == test_elements[i] + + +def test_add_chunking_strategy_to_partition_csv_non_default(): + filename = "example-docs/stanley-cups.csv" + + elements = partition_csv(filename=filename) + chunk_elements = partition_csv( + filename, + chunking_strategy="by_title", + max_characters=9, + combine_text_under_n_chars=0, + ) + chunks = chunk_by_title(elements, max_characters=9, combine_text_under_n_chars=0) + assert chunk_elements != elements + assert chunk_elements == chunks diff --git a/test_unstructured/partition/docx/test_docx.py b/test_unstructured/partition/docx/test_docx.py index 9c82c9d471..c622c390b4 100644 --- a/test_unstructured/partition/docx/test_docx.py +++ b/test_unstructured/partition/docx/test_docx.py @@ -18,6 +18,7 @@ ListItem, NarrativeText, Table, + TableChunk, Text, Title, ) @@ -422,14 +423,6 @@ def test_partition_docx_with_json(mock_document, expected_elements, tmpdir): assert elements[i] == test_elements[i] -def test_add_chunking_strategy_on_partition_docx(filename="example-docs/handbook-1p.docx"): - chunk_elements = partition_docx(filename, chunking_strategy="by_title") - elements = partition_docx(filename) - chunks = chunk_by_title(elements) - assert chunk_elements != elements - assert chunk_elements == chunks - - def test_parse_category_depth_by_style(): partitioner = _DocxPartitioner("example-docs/category-level.docx", None, None, False, None) @@ -489,3 +482,37 @@ def test_parse_category_depth_by_style_name(): def test_parse_category_depth_by_style_ilvl(): partitioner = _DocxPartitioner(None, None, None, False, None) assert partitioner._parse_category_depth_by_style_ilvl() == 0 + + +def test_add_chunking_strategy_on_partition_docx_default_args( + filename="example-docs/handbook-1p.docx", +): + chunk_elements = partition_docx(filename, chunking_strategy="by_title") + elements = partition_docx(filename) + chunks = chunk_by_title(elements) + + assert chunk_elements != elements + assert chunk_elements == chunks + + +def test_add_chunking_strategy_on_partition_docx( + filename="example-docs/fake-doc-emphasized-text.docx", +): + chunk_elements = partition_docx( + filename, + chunking_strategy="by_title", + max_characters=9, + combine_text_under_n_chars=5, + ) + elements = partition_docx(filename) + chunks = chunk_by_title(elements, max_characters=9, combine_text_under_n_chars=5) + # remove the last element of the TableChunk list because it will be the leftover slice + # and not necessarily the max_characters len + table_chunks = [chunk for chunk in chunks if isinstance(chunk, TableChunk)][:-1] + other_chunks = [chunk for chunk in chunks if not isinstance(chunk, TableChunk)] + for table_chunk in table_chunks: + assert len(table_chunk.text) == 9 + for chunk in other_chunks: + assert len(chunk.text) >= 5 + assert chunk_elements != elements + assert chunk_elements == chunks diff --git a/test_unstructured/partition/epub/test_epub.py b/test_unstructured/partition/epub/test_epub.py index 7d0e741899..991ec1991f 100644 --- a/test_unstructured/partition/epub/test_epub.py +++ b/test_unstructured/partition/epub/test_epub.py @@ -193,3 +193,24 @@ def test_add_chunking_strategy_on_partition_epub( chunks = chunk_by_title(elements) assert chunk_elements != elements assert chunk_elements == chunks + + +def test_add_chunking_strategy_on_partition_epub_non_default( + filename=os.path.join(DIRECTORY, "..", "..", "..", "example-docs", "winter-sports.epub"), +): + elements = partition_epub(filename=filename) + chunk_elements = partition_epub( + filename, + chunking_strategy="by_title", + max_characters=5, + new_after_n_chars=5, + combine_text_under_n_chars=0, + ) + chunks = chunk_by_title( + elements, + max_characters=5, + new_after_n_chars=5, + combine_text_under_n_chars=0, + ) + assert chunk_elements != elements + assert chunk_elements == chunks diff --git a/test_unstructured/partition/markdown/test_md.py b/test_unstructured/partition/markdown/test_md.py index 33d131b7a3..c73247998c 100644 --- a/test_unstructured/partition/markdown/test_md.py +++ b/test_unstructured/partition/markdown/test_md.py @@ -276,7 +276,7 @@ def test_partition_md_with_json( assert elements[i] == test_elements[i] -def test_add_chunking_strategy_on_partition_md( +def test_add_chunking_strategy_by_title_on_partition_md( filename="example-docs/README.md", ): elements = partition_md(filename=filename) diff --git a/test_unstructured/partition/msg/test_msg.py b/test_unstructured/partition/msg/test_msg.py index 6e179987a2..7678a6cda5 100644 --- a/test_unstructured/partition/msg/test_msg.py +++ b/test_unstructured/partition/msg/test_msg.py @@ -285,7 +285,7 @@ def test_partition_msg_with_pgp_encrypted_message( assert "Encrypted email detected" in caplog.text -def test_add_chunking_strategy_on_partition_msg( +def test_add_chunking_strategy_by_title_on_partition_msg( filename=os.path.join(EXAMPLE_DOCS_DIRECTORY, "fake-email.msg"), ): elements = partition_msg(filename=filename) diff --git a/test_unstructured/partition/odt/test_odt.py b/test_unstructured/partition/odt/test_odt.py index 9fe9b4b99d..982a11f9b4 100644 --- a/test_unstructured/partition/odt/test_odt.py +++ b/test_unstructured/partition/odt/test_odt.py @@ -2,7 +2,7 @@ import pathlib from unstructured.chunking.title import chunk_by_title -from unstructured.documents.elements import Table, Title +from unstructured.documents.elements import Table, TableChunk, Title from unstructured.partition.json import partition_json from unstructured.partition.odt import partition_odt from unstructured.staging.base import elements_to_json @@ -169,3 +169,24 @@ def test_add_chunking_strategy_on_partition_odt( chunks = chunk_by_title(elements) assert chunk_elements != elements assert chunk_elements == chunks + + +def test_add_chunking_strategy_on_partition_odt_non_default(): + filename = os.path.join(EXAMPLE_DOCS_DIRECTORY, "fake.odt") + elements = partition_odt(filename=filename) + chunk_elements = partition_odt( + filename, + chunking_strategy="by_title", + max_characters=7, + combine_text_under_n_chars=5, + ) + chunks = chunk_by_title( + elements, + max_characters=7, + combine_text_under_n_chars=5, + ) + for chunk in chunk_elements: + if isinstance(chunk, TableChunk): + assert len(chunk.text) <= 7 + assert chunk_elements != elements + assert chunk_elements == chunks diff --git a/test_unstructured/partition/pdf-image/test_image.py b/test_unstructured/partition/pdf-image/test_image.py index e2c9496356..721eed64dd 100644 --- a/test_unstructured/partition/pdf-image/test_image.py +++ b/test_unstructured/partition/pdf-image/test_image.py @@ -460,6 +460,25 @@ def test_add_chunking_strategy_on_partition_image( assert chunk_elements == chunks +def test_add_chunking_strategy_on_partition_image_hi_res( + filename="example-docs/layout-parser-paper-with-table.jpg", +): + elements = image.partition_image( + filename=filename, + strategy="hi_res", + infer_table_structure=True, + ) + chunk_elements = image.partition_image( + filename, + strategy="hi_res", + infer_table_structure=True, + chunking_strategy="by_title", + ) + chunks = chunk_by_title(elements) + assert chunk_elements != elements + assert chunk_elements == chunks + + def test_partition_image_uses_model_name(): with mock.patch.object( pdf, diff --git a/test_unstructured/partition/pdf-image/test_pdf.py b/test_unstructured/partition/pdf-image/test_pdf.py index e14a793a2a..37af371598 100644 --- a/test_unstructured/partition/pdf-image/test_pdf.py +++ b/test_unstructured/partition/pdf-image/test_pdf.py @@ -838,7 +838,7 @@ def test_partition_pdf_with_ocr_coordinates_are_not_nan_from_file( assert point[1] is not math.nan -def test_add_chunking_strategy_on_partition_pdf( +def test_add_chunking_strategy_by_title_on_partition_pdf( filename="example-docs/layout-parser-paper-fast.pdf", ): elements = pdf.partition_pdf(filename=filename) diff --git a/test_unstructured/partition/pptx/test_ppt.py b/test_unstructured/partition/pptx/test_ppt.py index 1662002ddd..3750e0e9c6 100644 --- a/test_unstructured/partition/pptx/test_ppt.py +++ b/test_unstructured/partition/pptx/test_ppt.py @@ -174,7 +174,7 @@ def test_partition_ppt_with_json( assert elements[i] == test_elements[i] -def test_add_chunking_strategy_on_partition_ppt( +def test_add_chunking_strategy_by_title_on_partition_ppt( filename=os.path.join(EXAMPLE_DOCS_DIRECTORY, "fake-power-point.ppt"), ): elements = partition_ppt(filename=filename) diff --git a/test_unstructured/partition/pptx/test_pptx.py b/test_unstructured/partition/pptx/test_pptx.py index 3540c020e7..37e9b7ce3e 100644 --- a/test_unstructured/partition/pptx/test_pptx.py +++ b/test_unstructured/partition/pptx/test_pptx.py @@ -371,8 +371,8 @@ def test_partition_pptx_with_json(): assert elements[i] == test_elements[i] -def test_add_chunking_strategy_on_partition_pptx(): - filename = os.path.join(EXAMPLE_DOCS_DIRECTORY, "fake-power-point.pptx") +def test_add_chunking_strategy_by_title_on_partition_pptx(): + filename = os.path.join(EXAMPLE_DOCS_DIRECTORY, "science-exploration-1p.pptx") elements = partition_pptx(filename=filename) chunk_elements = partition_pptx(filename, chunking_strategy="by_title") chunks = chunk_by_title(elements) diff --git a/test_unstructured/partition/pypandoc/test_org.py b/test_unstructured/partition/pypandoc/test_org.py index 9017c5e86f..81ad6d4ed2 100644 --- a/test_unstructured/partition/pypandoc/test_org.py +++ b/test_unstructured/partition/pypandoc/test_org.py @@ -136,7 +136,7 @@ def test_partition_org_with_json(filename="example-docs/README.org"): assert elements[i] == test_elements[i] -def test_add_chunking_strategy_on_partition_org( +def test_add_chunking_strategy_by_title_on_partition_org( filename="example-docs/README.org", ): elements = partition_org(filename=filename) diff --git a/test_unstructured/partition/test_auto.py b/test_unstructured/partition/test_auto.py index dcacf01ba2..a0c907aad3 100644 --- a/test_unstructured/partition/test_auto.py +++ b/test_unstructured/partition/test_auto.py @@ -17,6 +17,7 @@ ListItem, NarrativeText, Table, + TableChunk, Text, Title, ) @@ -937,37 +938,45 @@ def test_get_partition_with_extras_prompts_for_install_if_missing(): def test_add_chunking_strategy_on_partition_auto(): filename = "example-docs/example-10k-1p.html" - chunk_elements = partition(filename, chunking_strategy="by_title") elements = partition(filename) + chunk_elements = partition(filename, chunking_strategy="by_title") chunks = chunk_by_title(elements) assert chunk_elements != elements assert chunk_elements == chunks -def test_add_chunking_strategy_on_partition_auto_respects_multipage(): +def test_add_chunking_strategy_title_on_partition_auto_respects_multipage(): filename = "example-docs/example-10k-1p.html" partitioned_elements_multipage_false_combine_chars_0 = partition( filename, chunking_strategy="by_title", multipage_sections=False, - combine_under_n_chars=0, + combine_text_under_n_chars=0, + new_after_n_chars=300, + max_characters=400, ) partitioned_elements_multipage_true_combine_chars_0 = partition( filename, chunking_strategy="by_title", multipage_sections=True, - combine_under_n_chars=0, + combine_text_under_n_chars=0, + new_after_n_chars=300, + max_characters=400, ) elements = partition(filename) cleaned_elements_multipage_false_combine_chars_0 = chunk_by_title( elements, multipage_sections=False, - combine_under_n_chars=0, + combine_text_under_n_chars=0, + new_after_n_chars=300, + max_characters=400, ) cleaned_elements_multipage_true_combine_chars_0 = chunk_by_title( elements, multipage_sections=True, - combine_under_n_chars=0, + combine_text_under_n_chars=0, + new_after_n_chars=300, + max_characters=400, ) assert ( partitioned_elements_multipage_false_combine_chars_0 @@ -980,3 +989,69 @@ def test_add_chunking_strategy_on_partition_auto_respects_multipage(): assert len(partitioned_elements_multipage_true_combine_chars_0) != len( partitioned_elements_multipage_false_combine_chars_0, ) + + +def test_add_chunking_strategy_on_partition_auto_respects_max_chars(): + filename = "example-docs/example-10k-1p.html" + + # default chunk size in chars is 200 + partitioned_table_elements_200_chars = [ + e + for e in partition( + filename, + chunking_strategy="by_title", + max_characters=200, + combine_text_under_n_chars=5, + ) + if isinstance(e, (Table, TableChunk)) + ] + + partitioned_table_elements_5_chars = [ + e + for e in partition( + filename, + chunking_strategy="by_title", + max_characters=5, + combine_text_under_n_chars=5, + ) + if isinstance(e, (Table, TableChunk)) + ] + + elements = partition(filename) + + table_elements = [e for e in elements if isinstance(e, Table)] + + assert len(partitioned_table_elements_5_chars) != len(table_elements) + assert len(partitioned_table_elements_200_chars) != len(table_elements) + + assert len(partitioned_table_elements_5_chars[0].text) == 5 + assert len(partitioned_table_elements_5_chars[0].metadata.text_as_html) == 5 + + # the first table element is under 200 chars so doesn't get chunked! + assert table_elements[0] == partitioned_table_elements_200_chars[0] + assert len(partitioned_table_elements_200_chars[0].text) < 200 + assert len(partitioned_table_elements_200_chars[1].text) == 200 + assert len(partitioned_table_elements_200_chars[1].metadata.text_as_html) == 200 + + +def test_add_chunking_strategy_chars_on_partition_auto_adds_is_continuation(): + filename = "example-docs/example-10k-1p.html" + + # default chunk size in chars is 200 + partitioned_table_elements_200_chars = [ + e + for e in partition( + filename, + chunking_strategy="by_num_characters", + ) + if isinstance(e, Table) + ] + + i = 0 + for table in partitioned_table_elements_200_chars: + # have to reset the counter to 0 here when we encounter a Table element + if isinstance(table, Table): + i = 0 + if i > 0 and isinstance(table, TableChunk): + assert table.metadata.is_continuation is True + i += 1 diff --git a/unstructured/__version__.py b/unstructured/__version__.py index 5f8fd628c9..a4cf981717 100644 --- a/unstructured/__version__.py +++ b/unstructured/__version__.py @@ -1 +1 @@ -__version__ = "0.10.19-dev6" # pragma: no cover +__version__ = "0.10.19-dev7" # pragma: no cover diff --git a/unstructured/chunking/title.py b/unstructured/chunking/title.py index 8fd38d62f3..0c5bde799c 100644 --- a/unstructured/chunking/title.py +++ b/unstructured/chunking/title.py @@ -1,6 +1,7 @@ +import copy import functools import inspect -from typing import Any, Callable, Dict, List, TypeVar +from typing import Any, Callable, Dict, List, Optional, TypeVar, Union from typing_extensions import ParamSpec @@ -9,96 +10,130 @@ Element, ElementMetadata, Table, + TableChunk, Text, Title, ) +def chunk_table_element( + element: Table, + max_characters: Optional[int] = 500, +) -> List[Union[Table, TableChunk]]: + text = element.text + html = getattr(element, "text_as_html", None) + + if len(text) <= max_characters and ( # type: ignore + html is None or len(html) <= max_characters # type: ignore + ): + return [element] + + chunks: List[Union[Table, TableChunk]] = [] + metadata = copy.copy(element.metadata) + is_continuation = False + + while text or html: + text_chunk, text = text[:max_characters], text[max_characters:] + table_chunk = TableChunk(text=text_chunk, metadata=copy.copy(metadata)) + + if html: + html_chunk, html = html[:max_characters], html[max_characters:] + table_chunk.metadata.text_as_html = html_chunk + + if is_continuation: + table_chunk.metadata.is_continuation = True + + chunks.append(table_chunk) + is_continuation = True + + return chunks + + def chunk_by_title( elements: List[Element], multipage_sections: bool = True, - combine_under_n_chars: int = 500, - new_after_n_chars: int = 1500, + combine_text_under_n_chars: int = 500, + new_after_n_chars: int = 500, + max_characters: int = 500, ) -> List[Element]: """Uses title elements to identify sections within the document for chunking. Splits off into a new section when a title is detected or if metadata changes, which happens when page numbers or sections change. Cuts off sections once they have exceeded - a character length of new_after_n_chars. + a character length of max_characters. Parameters ---------- elements - A list of unstructured elements. Usually the ouput of a partition functions. + A list of unstructured elements. Usually the output of a partition functions. multipage_sections If True, sections can span multiple pages. Defaults to True. - combine_under_n_chars + combine_text_under_n_chars Combines elements (for example a series of titles) until a section reaches a length of n characters. new_after_n_chars - Cuts off new sections once they reach a length of n characters + Cuts off new sections once they reach a length of n characters (soft max) + max_characters + Chunks table elements text and text_as_html into chunks of length n characters (hard max) + TODO: (amanda) extend to other elements """ if ( - combine_under_n_chars is not None + combine_text_under_n_chars is not None and new_after_n_chars is not None + and max_characters is not None and ( - combine_under_n_chars > new_after_n_chars - or combine_under_n_chars < 0 + combine_text_under_n_chars > new_after_n_chars + or combine_text_under_n_chars < 0 or new_after_n_chars < 0 + or max_characters <= 0 + or combine_text_under_n_chars > max_characters ) ): raise ValueError( - "Invalid values for combine_under_n_chars and/or new_after_n_chars.", + "Invalid values for combine_text_under_n_chars and/or max_characters.", ) chunked_elements: List[Element] = [] sections = _split_elements_by_title_and_table( elements, multipage_sections=multipage_sections, - combine_under_n_chars=combine_under_n_chars, + combine_text_under_n_chars=combine_text_under_n_chars, new_after_n_chars=new_after_n_chars, ) - for section in sections: if not section: continue - if not isinstance(section[0], Text) or isinstance(section[0], Table): - chunked_elements.extend(section) - elif isinstance(section[0], Text): - text = "" - metadata = section[0].metadata + first_element = section[0] - for i, element in enumerate(section): - if isinstance(element, Text): - text += "\n\n" if text else "" - start_char = len(text) - text += element.text + if not isinstance(first_element, Text): + chunked_elements.extend(section) + continue - for attr, value in vars(element.metadata).items(): - if not isinstance(value, list): - continue + elif isinstance(first_element, Table): + chunked_elements.extend(chunk_table_element(first_element, max_characters)) + continue - _value = getattr(metadata, attr, []) - if _value is None: - _value = [] + text = "" + metadata = first_element.metadata + start_char = 0 + for element in section: + if isinstance(element, Text): + text += "\n\n" if text else "" + start_char = len(text) + text += element.text + for attr, value in vars(element.metadata).items(): + if isinstance(value, list): + _value = getattr(metadata, attr, []) or [] if attr == "regex_metadata": for item in value: item["start"] += start_char item["end"] += start_char - if i > 0: - # NOTE(newelh): Previously, _value was extended with value. - # This caused a memory error if the content was a list of strings - # with a large number of elements -- doubling the list size each time. - # This now instead ensures that the _value list is unique and updated. - for item in value: - if item not in _value: - _value.append(item) - - setattr(metadata, attr, _value) + _value.extend(item for item in value if item not in _value) + setattr(metadata, attr, _value) - chunked_elements.append(CompositeElement(text=text, metadata=metadata)) + chunked_elements.append(CompositeElement(text=text, metadata=metadata)) return chunked_elements @@ -106,8 +141,8 @@ def chunk_by_title( def _split_elements_by_title_and_table( elements: List[Element], multipage_sections: bool = True, - combine_under_n_chars: int = 500, - new_after_n_chars: int = 1500, + combine_text_under_n_chars: int = 500, + new_after_n_chars: int = 500, ) -> List[List[Element]]: sections: List[List[Element]] = [] section: List[Element] = [] @@ -123,11 +158,11 @@ def _split_elements_by_title_and_table( ) section_length = sum([len(str(element)) for element in section]) - new_section = (isinstance(element, Title) and section_length > combine_under_n_chars) or ( - not metadata_matches or section_length > new_after_n_chars - ) + new_section = ( + isinstance(element, Title) and section_length > combine_text_under_n_chars + ) or (not metadata_matches or section_length > new_after_n_chars) - if isinstance(element, Table) or not isinstance(element, Text): + if not isinstance(element, Text) or isinstance(element, Table): sections.append(section) sections.append([element]) section = [] @@ -185,7 +220,7 @@ def add_chunking_strategy() -> Callable[[Callable[_P, List[Element]]], Callable[ """Decorator for chuncking text. Uses title elements to identify sections within the document for chunking. Splits off a new section when a title is detected or if metadata changes, which happens when page numbers or sections change. Cuts off sections once they have exceeded - a character length of new_after_n_chars.""" + a character length of max_characters.""" def decorator(func: Callable[_P, List[Element]]) -> Callable[_P, List[Element]]: if func.__doc__ and ( @@ -199,11 +234,15 @@ def decorator(func: Callable[_P, List[Element]]) -> Callable[_P, List[Element]]: + "\n\tAdditional Parameters:" + "\n\t\tmultipage_sections" + "\n\t\t\tIf True, sections can span multiple pages. Defaults to True." - + "\n\t\tcombine_under_n_chars" + + "\n\t\tcombine_text_under_n_chars" + "\n\t\t\tCombines elements (for example a series of titles) until a section" + "\n\t\t\treaches a length of n characters." + "\n\t\tnew_after_n_chars" - + "\n\t\t\tCuts off new sections once they reach a length of n characters" + + "\n\t\t\t Cuts off new sections once they reach a length of n characters" + + "\n\t\t\t a soft max." + + "\n\t\tmax_characters" + + "\n\t\t\tChunks table elements text and text_as_html into chunks" + + "\n\t\t\tof length n characters, a hard max." ) @functools.wraps(func) @@ -218,8 +257,9 @@ def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> List[Element]: elements = chunk_by_title( elements, multipage_sections=params.get("multipage_sections", True), - combine_under_n_chars=params.get("combine_under_n_chars", 500), - new_after_n_chars=params.get("new_after_n_chars", 1500), + combine_text_under_n_chars=params.get("combine_text_under_n_chars", 500), + new_after_n_chars=params.get("new_after_n_chars", 500), + max_characters=params.get("max_characters", 500), ) return elements diff --git a/unstructured/documents/elements.py b/unstructured/documents/elements.py index f051e1b4f6..75c15a3e36 100644 --- a/unstructured/documents/elements.py +++ b/unstructured/documents/elements.py @@ -185,6 +185,10 @@ class ElementMetadata: # Metadata extracted via regex regex_metadata: Optional[Dict[str, List[RegexMetadata]]] = None + # Chunking metadata fields + num_characters: Optional[int] = None + is_continuation: Optional[bool] = None + # Detection Model Class Probabilities from Unstructured-Inference Hi-Res detection_class_prob: Optional[float] = None @@ -566,6 +570,14 @@ class Table(Text): pass +class TableChunk(Table): + """An element for capturing chunks of tables.""" + + category = "Table" + + pass + + class Header(Text): """An element for capturing document headers.""" diff --git a/unstructured/ingest/interfaces.py b/unstructured/ingest/interfaces.py index c76fdfb783..caefa50afd 100644 --- a/unstructured/ingest/interfaces.py +++ b/unstructured/ingest/interfaces.py @@ -83,16 +83,16 @@ def get_embedder(self) -> BaseEmbeddingEncoder: class ChunkingConfig(BaseConfig): chunk_elements: bool = False multipage_sections: bool = True - combine_under_n_chars: int = 500 - new_after_n_chars: int = 1500 + combine_text_under_n_chars: int = 500 + max_characters: int = 1500 def chunk(self, elements: t.List[Element]) -> t.List[Element]: if self.chunk_elements: return chunk_by_title( elements=elements, multipage_sections=self.multipage_sections, - combine_under_n_chars=self.combine_under_n_chars, - new_after_n_chars=self.new_after_n_chars, + combine_text_under_n_chars=self.combine_text_under_n_chars, + max_characters=self.max_characters, ) else: return elements diff --git a/unstructured/partition/csv.py b/unstructured/partition/csv.py index 6a7314de03..2528f321cd 100644 --- a/unstructured/partition/csv.py +++ b/unstructured/partition/csv.py @@ -4,6 +4,7 @@ import pandas as pd from lxml.html.soupparser import fromstring as soupparser_fromstring +from unstructured.chunking.title import add_chunking_strategy from unstructured.documents.elements import ( Element, ElementMetadata, @@ -21,6 +22,7 @@ @process_metadata() @add_metadata_with_filetype(FileType.CSV) +@add_chunking_strategy() def partition_csv( filename: Optional[str] = None, file: Optional[Union[IO[bytes], SpooledTemporaryFile]] = None, diff --git a/unstructured/partition/xlsx.py b/unstructured/partition/xlsx.py index 2f4538210f..ebffd6cdf9 100644 --- a/unstructured/partition/xlsx.py +++ b/unstructured/partition/xlsx.py @@ -4,6 +4,7 @@ import pandas as pd from lxml.html.soupparser import fromstring as soupparser_fromstring +from unstructured.chunking.title import add_chunking_strategy from unstructured.documents.elements import ( Element, ElementMetadata, @@ -21,6 +22,7 @@ @process_metadata() @add_metadata_with_filetype(FileType.XLSX) +@add_chunking_strategy() def partition_xlsx( filename: Optional[str] = None, file: Optional[Union[IO[bytes], SpooledTemporaryFile]] = None, diff --git a/unstructured/staging/weaviate.py b/unstructured/staging/weaviate.py index c6efc80bd4..4a4e15276c 100644 --- a/unstructured/staging/weaviate.py +++ b/unstructured/staging/weaviate.py @@ -15,6 +15,7 @@ class Properties(TypedDict): "regex_metadata", "emphasized_texts", "detection_class_prob", + "is_continuation", ) From 8821689f3659eaa98a01b22a3136996ceff27cf2 Mon Sep 17 00:00:00 2001 From: Roman Isecke <136338424+rbiseck3@users.noreply.github.com> Date: Tue, 3 Oct 2023 14:31:28 -0400 Subject: [PATCH 4/5] Roman/s3 minio all cloud support (#1606) ### Description Exposes the endpoint url as an access kwarg when using the s3 filesystem library via the fsspec abstraction. This allows for any non-aws data providers that support the s3 protocol to be used with the s3 connector (i.e. minio) Closes out https://github.com/Unstructured-IO/unstructured/issues/950 --------- Co-authored-by: ryannikolaidis <1208590+ryannikolaidis@users.noreply.github.com> Co-authored-by: rbiseck3 --- CHANGELOG.md | 8 ++-- .../create-and-check-minio.sh | 25 ++++++++++ .../minio-test-helpers/docker-compose.yaml | 13 ++++++ .../wiki_movie_plots_small.csv | 31 +++++++++++++ .../s3-minio/wiki_movie_plots_small.csv.json | 19 ++++++++ .../test-ingest-s3-minio.sh | 46 +++++++++++++++++++ test_unstructured_ingest/test-ingest.sh | 1 + unstructured/__version__.py | 2 +- unstructured/ingest/cli/cmds/s3.py | 9 ++++ unstructured/ingest/runner/s3.py | 6 ++- unstructured/ingest/runner/writers.py | 7 ++- 11 files changed, 161 insertions(+), 6 deletions(-) create mode 100755 scripts/minio-test-helpers/create-and-check-minio.sh create mode 100644 scripts/minio-test-helpers/docker-compose.yaml create mode 100644 scripts/minio-test-helpers/wiki_movie_plots_small.csv create mode 100644 test_unstructured_ingest/expected-structured-output/s3-minio/wiki_movie_plots_small.csv.json create mode 100755 test_unstructured_ingest/test-ingest-s3-minio.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d06dc15f9..23878a7c2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,16 +1,18 @@ -## 0.10.19-dev7 +## 0.10.19-dev8 ### Enhancements * **bump `unstructured-inference` to `0.6.6`** The updated version of `unstructured-inference` makes table extraction in `hi_res` mode configurable to fine tune table extraction performance; it also improves element detection by adding a deduplication post processing step in the `hi_res` partitioning of pdfs and images. * **Detect text in HTML Heading Tags as Titles** This will increase the accuracy of hierarchies in HTML documents and provide more accurate element categorization. If text is in an HTML heading tag and is not a list item, address, or narrative text, categorize it as a title. * **Update python-based docs** Refactor docs to use the actual unstructured code rather than using the subprocess library to run the cli command itself. -* * **Adds data source properties to SharePoint, Outlook, Onedrive, Reddit, and Slack connectors** These properties (date_created, date_modified, version, source_url, record_locator) are written to element metadata during ingest, mapping elements to information about the document source from which they derive. This functionality enables downstream applications to reveal source document applications, e.g. a link to a GDrive doc, Salesforce record, etc. +* **Adds data source properties to SharePoint, Outlook, Onedrive, Reddit, and Slack connectors** These properties (date_created, date_modified, version, source_url, record_locator) are written to element metadata during ingest, mapping elements to information about the document source from which they derive. This functionality enables downstream applications to reveal source document applications, e.g. a link to a GDrive doc, Salesforce record, etc. * **Adds Table support for the `add_chunking_strategy` decorator to partition functions.** In addition to combining elements under Title elements, user's can now specify the `max_characters=` argument to chunk Table elements into TableChunk elements with `text` and `text_as_html` of length characters. This means partitioned Table results are ready for use in downstream applications without any post processing. - +* **Expose endpoint url for s3 connectors** By allowing for the endpoint url to be explicitly overwritten, this allows for any non-AWS data providers supporting the s3 protocol to be supported (i.e. minio). ### Features +### Features + ### Fixes * **Fixes partition_pdf is_alnum reference bug** Problem: The `partition_pdf` when attempt to get bounding box from element experienced a reference before assignment error when the first object is not text extractable. Fix: Switched to a flag when the condition is met. Importance: Crucial to be able to partition with pdf. diff --git a/scripts/minio-test-helpers/create-and-check-minio.sh b/scripts/minio-test-helpers/create-and-check-minio.sh new file mode 100755 index 0000000000..09089a944a --- /dev/null +++ b/scripts/minio-test-helpers/create-and-check-minio.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +SCRIPT_DIR=$(dirname "$(realpath "$0")") + +secret_key=minioadmin +access_key=minioadmin +region=us-east-2 +endpoint_url=http://localhost:9000 +bucket_name=utic-dev-tech-fixtures + +function upload(){ + echo "Uploading test content to new bucket in minio" + AWS_REGION=$region AWS_SECRET_ACCESS_KEY=$secret_key AWS_ACCESS_KEY_ID=$access_key \ + aws --output json --endpoint-url $endpoint_url s3api create-bucket --bucket $bucket_name | jq + AWS_REGION=$region AWS_SECRET_ACCESS_KEY=$secret_key AWS_ACCESS_KEY_ID=$access_key \ + aws --endpoint-url $endpoint_url s3 cp "$SCRIPT_DIR"/wiki_movie_plots_small.csv s3://$bucket_name/ +} + +# Create Minio single server +docker compose version +docker compose -f "$SCRIPT_DIR"/docker-compose.yaml up --wait +docker compose -f "$SCRIPT_DIR"/docker-compose.yaml ps + +echo "Cluster is live." +upload diff --git a/scripts/minio-test-helpers/docker-compose.yaml b/scripts/minio-test-helpers/docker-compose.yaml new file mode 100644 index 0000000000..acc3ec9b48 --- /dev/null +++ b/scripts/minio-test-helpers/docker-compose.yaml @@ -0,0 +1,13 @@ +services: + minio: + image: quay.io/minio/minio + container_name: minio-test + ports: + - 9000:9000 + - 9001:9001 + command: server --console-address ":9001" /data + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] + interval: 5s + timeout: 20s + retries: 3 diff --git a/scripts/minio-test-helpers/wiki_movie_plots_small.csv b/scripts/minio-test-helpers/wiki_movie_plots_small.csv new file mode 100644 index 0000000000..2fbb2b49bb --- /dev/null +++ b/scripts/minio-test-helpers/wiki_movie_plots_small.csv @@ -0,0 +1,31 @@ +Release Year,Title,Origin/Ethnicity,Director,Cast,Genre,Wiki Page,Plot +1901,Kansas Saloon Smashers,American,Unknown,,unknown,https://en.wikipedia.org/wiki/Kansas_Saloon_Smashers,"A bartender is working at a saloon, serving drinks to customers. After he fills a stereotypically Irish man's bucket with beer, Carrie Nation and her followers burst inside. They assault the Irish man, pulling his hat over his eyes and then dumping the beer over his head. The group then begin wrecking the bar, smashing the fixtures, mirrors, and breaking the cash register. The bartender then sprays seltzer water in Nation's face before a group of policemen appear and order everybody to leave.[1]" +1901,Love by the Light of the Moon,American,Unknown,,unknown,https://en.wikipedia.org/wiki/Love_by_the_Light_of_the_Moon,"The moon, painted with a smiling face hangs over a park at night. A young couple walking past a fence learn on a railing and look up. The moon smiles. They embrace, and the moon's smile gets bigger. They then sit down on a bench by a tree. The moon's view is blocked, causing him to frown. In the last scene, the man fans the woman with his hat because the moon has left the sky and is perched over her shoulder to see everything better." +1901,The Martyred Presidents,American,Unknown,,unknown,https://en.wikipedia.org/wiki/The_Martyred_Presidents,"The film, just over a minute long, is composed of two shots. In the first, a girl sits at the base of an altar or tomb, her face hidden from the camera. At the center of the altar, a viewing portal displays the portraits of three U.S. Presidents—Abraham Lincoln, James A. Garfield, and William McKinley—each victims of assassination. +In the second shot, which runs just over eight seconds long, an assassin kneels feet of Lady Justice." +1901,"Terrible Teddy, the Grizzly King",American,Unknown,,unknown,"https://en.wikipedia.org/wiki/Terrible_Teddy,_the_Grizzly_King","Lasting just 61 seconds and consisting of two shots, the first shot is set in a wood during winter. The actor representing then vice-president Theodore Roosevelt enthusiastically hurries down a hillside towards a tree in the foreground. He falls once, but rights himself and cocks his rifle. Two other men, bearing signs reading ""His Photographer"" and ""His Press Agent"" respectively, follow him into the shot; the photographer sets up his camera. ""Teddy"" aims his rifle upward at the tree and fells what appears to be a common house cat, which he then proceeds to stab. ""Teddy"" holds his prize aloft, and the press agent takes notes. The second shot is taken in a slightly different part of the wood, on a path. ""Teddy"" rides the path on his horse towards the camera and out to the left of the shot, followed closely by the press agent and photographer, still dutifully holding their signs." +1902,Jack and the Beanstalk,American,"George S. Fleming, Edwin S. Porter",,unknown,https://en.wikipedia.org/wiki/Jack_and_the_Beanstalk_(1902_film),"The earliest known adaptation of the classic fairytale, this films shows Jack trading his cow for the beans, his mother forcing him to drop them in the front yard, and beig forced upstairs. As he sleeps, Jack is visited by a fairy who shows him glimpses of what will await him when he ascends the bean stalk. In this version, Jack is the son of a deposed king. When Jack wakes up, he finds the beanstalk has grown and he climbs to the top where he enters the giant's home. The giant finds Jack, who narrowly escapes. The giant chases Jack down the bean stalk, but Jack is able to cut it down before the giant can get to safety. He falls and is killed as Jack celebrates. The fairy then reveals that Jack may return home as a prince." +1903,Alice in Wonderland,American,Cecil Hepworth,May Clark,unknown,https://en.wikipedia.org/wiki/Alice_in_Wonderland_(1903_film),"Alice follows a large white rabbit down a ""Rabbit-hole"". She finds a tiny door. When she finds a bottle labeled ""Drink me"", she does, and shrinks, but not enough to pass through the door. She then eats something labeled ""Eat me"" and grows larger. She finds a fan when enables her to shrink enough to get into the ""Garden"" and try to get a ""Dog"" to play with her. She enters the ""White Rabbit's tiny House,"" but suddenly resumes her normal size. In order to get out, she has to use the ""magic fan."" +She enters a kitchen, in which there is a cook and a woman holding a baby. She persuades the woman to give her the child and takes the infant outside after the cook starts throwing things around. The baby then turns into a pig and squirms out of her grip. ""The Duchess's Cheshire Cat"" appears and disappears a couple of times to Alice and directs her to the Mad Hatter's ""Mad Tea-Party."" After a while, she leaves. +The Queen invites Alice to join the ""ROYAL PROCESSION"": a parade of marching playing cards and others headed by the White Rabbit. When Alice ""unintentionally offends the Queen"", the latter summons the ""Executioner"". Alice ""boxes the ears"", then flees when all the playing cards come for her. Then she wakes up and realizes it was all a dream." +1903,The Great Train Robbery,American,Edwin S. Porter,,western,https://en.wikipedia.org/wiki/The_Great_Train_Robbery_(1903_film),"The film opens with two bandits breaking into a railroad telegraph office, where they force the operator at gunpoint to have a train stopped and to transmit orders for the engineer to fill the locomotive's tender at the station's water tank. They then knock the operator out and tie him up. As the train stops it is boarded by the bandits‍—‌now four. Two bandits enter an express car, kill a messenger and open a box of valuables with dynamite; the others kill the fireman and force the engineer to halt the train and disconnect the locomotive. The bandits then force the passengers off the train and rifle them for their belongings. One passenger tries to escape but is instantly shot down. Carrying their loot, the bandits escape in the locomotive, later stopping in a valley where their horses had been left. +Meanwhile, back in the telegraph office, the bound operator awakens, but he collapses again. His daughter arrives bringing him his meal and cuts him free, and restores him to consciousness by dousing him with water. +There is some comic relief at a dance hall, where an Eastern stranger is forced to dance while the locals fire at his feet. The door suddenly opens and the telegraph operator rushes in to tell them of the robbery. The men quickly form a posse, which overtakes the bandits, and in a final shootout kills them all and recovers the stolen mail." +1904,The Suburbanite,American,Wallace McCutcheon,,comedy,https://en.wikipedia.org/wiki/The_Suburbanite,"The film is about a family who move to the suburbs, hoping for a quiet life. Things start to go wrong, and the wife gets violent and starts throwing crockery, leading to her arrest." +1905,The Little Train Robbery,American,Edwin Stanton Porter,,unknown,https://en.wikipedia.org/wiki/The_Little_Train_Robbery,"The opening scene shows the interior of the robbers' den. The walls are decorated with the portraits of notorious criminals and pictures illustrating the exploits of famous bandits. Some of the gang are lounging about, while others are reading novels and illustrated papers. Although of youthful appearance, each is dressed like a typical Western desperado. The ""Bandit Queen,"" leading a blindfolded new recruit, now enters the room. He is led to the center of the room, raises his right hand and is solemnly sworn in. When the bandage is removed from his eyes he finds himself looking into the muzzles of a dozen or more 45's. The gang then congratulates the new member and heartily shake his hand. The ""Bandit Queen"" who is evidently the leader of the gang, now calls for volunteers to hold up a train. All respond, but she picks out seven for the job who immediately leave the cabin. +The next scene shows the gang breaking into a barn. They steal ponies and ride away. Upon reaching the place agreed upon they picket their ponies and leaving them in charge of a trusted member proceed to a wild mountain spot in a bend of the railroad, where the road runs over a steep embankment. The spot is an ideal one for holding up a train. Cross ties are now placed on the railroad track and the gang hide in some bushes close by and wait for the train. The train soon approaches and is brought to a stop. The engineer leaves his engine and proceeds to remove the obstruction on the track. While he is bending over one of the gang sneaks up behind them and hits him on the head with an axe, and knocks him senseless down the embankment, while the gang surround the train and hold up the passengers. After securing all the ""valuables,"" consisting principally of candy and dolls, the robbers uncouple the engine and one car and make their escape just in time to avoid a posse of police who appear on the scene. Further up the road they abandon the engine and car, take to the woods and soon reach their ponies. +In the meantime the police have learned the particulars of the hold-up from the frightened passengers and have started up the railroad tracks after the fleeing robbers. The robbers are next seen riding up the bed of a shallow stream and finally reach their den, where the remainder of the gang have been waiting for them. Believing they have successfully eluded their pursuers, they proceed to divide the ""plunder."" The police, however, have struck the right trail and are in close pursuit. While the ""plunder"" is being divided a sentry gives the alarm and the entire gang, abandoning everything, rush from the cabin barely in time to escape capture. The police make a hurried search and again start in pursuit. The robbers are so hard pressed that they are unable to reach their ponies, and are obliged to take chances on foot. The police now get in sight of the fleeing robbers and a lively chase follows through tall weeds, over a bridge and up a steep hill. Reaching a pond the police are close on their heels. The foremost robbers jump in clothes and all and strike out for the opposite bank. Two hesitate and are captured. Boats are secured and after an exciting tussle the entire gang is rounded up. In the mix up one of the police is dragged overboard. The final scene shows the entire gang of bedraggled and crestfallen robbers tied together with a rope and being led away by the police. Two of the police are loaded down with revolvers, knives and cartridge belts, and resemble walking aresenals. As a fitting climax a confederate steals out of the woods, cuts the rope and gallantly rescues the ""Bandit Queen.""" +1905,The Night Before Christmas,American,Edwin Stanton Porter,,unknown,https://en.wikipedia.org/wiki/The_Night_Before_Christmas_(1905_film),"Scenes are introduced using lines of the poem.[2] Santa Claus, played by Harry Eytinge, is shown feeding real reindeer[4] and finishes his work in the workshop. Meanwhile, the children of a city household hang their stockings and go to bed, but unable to sleep they engage in a pillow fight. Santa Claus leaves his home on a sleigh with his reindeer. He enters the children's house through the chimney, and leaves the presents. The children come down the stairs and enjoy their presents." +1906,Dream of a Rarebit Fiend,American,Wallace McCutcheon and Edwin S. Porter,,short,https://en.wikipedia.org/wiki/Dream_of_a_Rarebit_Fiend_(1906_film),"The Rarebit Fiend gorges on Welsh rarebit at a restaurant. When he leaves, he begins to get dizzy as he starts to hallucinate. He desperately tries to hang onto a lamppost as the world spins all around him. A man helps him get home. He falls into bed and begins having more hallucinatory dreams. During a dream sequence, the furniture begins moving around the room. Imps emerge from a floating Welsh rarebit container and begin poking his head as he sleeps. His bed then begins dancing and spinning wildly around the room before flying out the window with the Fiend in it. The bed floats across the city as the Fiend floats up and off the bed. He hangs off the back and eventually gets caught on a weathervane atop a steeple. His bedclothes tear and he falls from the sky, crashing through his bedroom ceiling. The Fiend awakens from the dream after falling out of his bed." +1906,From Leadville to Aspen: A Hold-Up in the Rockies,American,Francis J. Marion and Wallace McCutcheon,,short action/crime western,https://en.wikipedia.org/wiki/From_Leadville_to_Aspen:_A_Hold-Up_in_the_Rockies,The film features a train traveling through the Rockies and a hold up created by two thugs placing logs on the line. They systematically rob the wealthy occupants at gunpoint and then make their getaway along the tracks and later by a hi-jacked horse and cart. +1906,Kathleen Mavourneen,American,Edwin S. Porter,,short film,https://en.wikipedia.org/wiki/Kathleen_Mavourneen_(1906_film),"Irish villager Kathleen is a tenant of Captain Clearfield, who controls local judges and criminals. Her father owes Clearfield a large debt. Terence O'More saves the village from Clearfield, causing a large celebration. +Film historian Charles Musser writes of Porter's adaptation, ""O'More not only rescues Kathleen from the villain but, through marriage, renews the family for another generation.""[1]" +1907,Daniel Boone,American,Wallace McCutcheon and Ediwin S. Porter,"William Craven, Florence Lawrence",biographical,https://en.wikipedia.org/wiki/Daniel_Boone_(1907_film),"Boone's daughter befriends an Indian maiden as Boone and his companion start out on a hunting expedition. While he is away, Boone's cabin is attacked by the Indians, who set it on fire and abduct Boone's daughter. Boone returns, swears vengeance, then heads out on the trail to the Indian camp. His daughter escapes but is chased. The Indians encounter Boone, which sets off a huge fight on the edge of a cliff. A burning arrow gets shot into the Indian camp. Boone gets tied to the stake and tortured. The burning arrow sets the Indian camp on fire, causing panic. Boone is rescued by his horse, and Boone has a knife fight in which he kills the Indian chief.[2]" +1907,How Brown Saw the Baseball Game,American,Unknown,Unknown,comedy,https://en.wikipedia.org/wiki/How_Brown_Saw_the_Baseball_Game,"Before heading out to a baseball game at a nearby ballpark, sports fan Mr. Brown drinks several highball cocktails. He arrives at the ballpark to watch the game, but has become so inebriated that the game appears to him in reverse, with the players running the bases backwards and the baseball flying back into the pitcher's hand. After the game is over, Mr. Brown is escorted home by one of his friends. When they arrive at Brown's house, they encounter his wife who becomes furious with the friend and proceeds to physically assault him, believing he is responsible for her husband's severe intoxication.[1]" +1907,Laughing Gas,American,Edwin Stanton Porter,"Bertha Regustus, Edward Boulden",comedy,https://en.wikipedia.org/wiki/Laughing_Gas_(film)#1907_Film,"The plot is that of a black woman going to the dentist for a toothache and being given laughing gas. On her way walking home, and in other situations, she can't stop laughing, and everyone she meets ""catches"" the laughter from her, including a vendor and police officers." +1908,The Adventures of Dollie,American,D. W. Griffith,"Arthur V. Johnson, Linda Arvidson",drama,https://en.wikipedia.org/wiki/The_Adventures_of_Dollie,"On a beautiful summer day a father and mother take their daughter Dollie on an outing to the river. The mother refuses to buy a gypsy's wares. The gypsy tries to rob the mother, but the father drives him off. The gypsy returns to the camp and devises a plan. They return and kidnap Dollie while her parents are distracted. A rescue crew is organized, but the gypsy takes Dollie to his camp. They gag Dollie and hide her in a barrel before the rescue party gets to the camp. Once they leave the gypsies and escapes in their wagon. As the wagon crosses the river, the barrel falls into the water. Still sealed in the barrel, Dollie is swept downstream in dangerous currents. A boy who is fishing in the river finds the barrel, and Dollie is reunited safely with her parents." +1908,The Black Viper,American,D. W. Griffith,D. W. Griffith,drama,https://en.wikipedia.org/wiki/The_Black_Viper,"A thug accosts a girl as she leaves her workplace but a man rescues her. The thug vows revenge and, with the help of two friends, attacks the girl and her rescuer again as they're going for a walk. This time they succeed in kidnapping the rescuer. He is bound and gagged and taken away in a cart. The girl runs home and gets help from several neighbors. They track the ruffians down to a cabin in the mountains where the gang has trapped their victim and set the cabin on fire. A thug and Rescuer fight on the roof of the house." +1908,A Calamitous Elopement,American,D.W. Griffith,"Harry Solter, Linda Arvidson",comedy,https://en.wikipedia.org/wiki/A_Calamitous_Elopement,"A young couple decides to elope after being caught in the midst of a romantic moment by the woman's angry father. They make plans to leave, but a thief discovers their plans and hides in their trunk and waits for the right moment to steal their belongings." +1908,The Call of the Wild,American,D. W. Griffith,Charles Inslee,adventure,https://en.wikipedia.org/wiki/The_Call_of_the_Wild_(1908_film),"A white girl (Florence Lawrence) rejects a proposal from an Indian brave (Charles Inslee) in this early one-reel Western melodrama. Despite the rejection, the Indian still comes to the girl's defense when she is abducted by his warring tribe. In her first year in films, Florence Lawrence was already the most popular among the Biograph Company's anonymous stock company players. By 1909, she was known the world over as ""The Biograph Girl.""" +1908,A Christmas Carol,American,Unknown,Tom Ricketts,drama,https://en.wikipedia.org/wiki/A_Christmas_Carol_(1908_film),"No prints of the first American film adaptation of A Christmas Carol are known to exist,[1] but The Moving Picture World magazine provided a scene-by-scene description before the film's release.[2] Scrooge goes into his office and begins working. His nephew, along with three women who wish for Scrooge to donate enter. However, Scrooge dismisses them. On the night of Christmas Eve, his long-dead partner Jacob Marley comes as a ghost, warning him of a horrible fate if he does not change his ways. Scrooge meets three spirits that show Scrooge the real meaning of Christmas, along with his grave, the result of his parsimonious ways. The next morning, he wakes and realizes the error of his ways. Scrooge was then euphoric and generous for the rest of his life." +1908,The Fight for Freedom,American,D. W. Griffith,"Florence Auer, John G. Adolfi",western,https://en.wikipedia.org/wiki/The_Fight_for_Freedom,"The film opens in a town on the Mexican border. A poker game is going on in the local saloon. One of the players cheats and is shot dead by another of the players, a Mexican named Pedro. In the uproar that follows Pedro is wounded as he escapes from the saloon. The sheriff is called, who tracks Pedro to his home but Pedro kills the sherriff too. While Pedro hides, his wife Juanita, is arrested on suspicion of murdering the sheriff. Pedro rescues her from the town jail and the two head for the Mexican border. Caught by the posse before they reach the border, Juanita is killed and the film ends with Pedro being arrested and taken back to town." diff --git a/test_unstructured_ingest/expected-structured-output/s3-minio/wiki_movie_plots_small.csv.json b/test_unstructured_ingest/expected-structured-output/s3-minio/wiki_movie_plots_small.csv.json new file mode 100644 index 0000000000..0a44c84aba --- /dev/null +++ b/test_unstructured_ingest/expected-structured-output/s3-minio/wiki_movie_plots_small.csv.json @@ -0,0 +1,19 @@ +[ + { + "type": "Table", + "element_id": "f078b58f281b4e231430e34a3ece07f3", + "metadata": { + "data_source": { + "url": "s3://utic-dev-tech-fixtures/wiki_movie_plots_small.csv", + "version": 103589111396252091980300895568390462924, + "record_locator": { + "protocol": "s3", + "remote_file_path": "utic-dev-tech-fixtures/wiki_movie_plots_small.csv" + } + }, + "filetype": "text/csv", + "text_as_html": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    1901Kansas Saloon SmashersAmericanUnknownunknownhttps://en.wikipedia.org/wiki/Kansas_Saloon_SmashersA bartender is working at a saloon, serving drinks to customers. After he fills a stereotypically Irish man's bucket with beer, Carrie Nation and her followers burst inside. They assault the Irish man, pulling his hat over his eyes and then dumping the beer over his head. The group then begin wrecking the bar, smashing the fixtures, mirrors, and breaking the cash register. The bartender then sprays seltzer water in Nation's face before a group of policemen appear and order everybody to leave.[1]
    1901Love by the Light of the MoonAmericanUnknownunknownhttps://en.wikipedia.org/wiki/Love_by_the_Light_of_the_MoonThe moon, painted with a smiling face hangs over a park at night. A young couple walking past a fence learn on a railing and look up. The moon smiles. They embrace, and the moon's smile gets bigger. They then sit down on a bench by a tree. The moon's view is blocked, causing him to frown. In the last scene, the man fans the woman with his hat because the moon has left the sky and is perched over her shoulder to see everything better.
    1901The Martyred PresidentsAmericanUnknownunknownhttps://en.wikipedia.org/wiki/The_Martyred_PresidentsThe film, just over a minute long, is composed of two shots. In the first, a girl sits at the base of an altar or tomb, her face hidden from the camera. At the center of the altar, a viewing portal displays the portraits of three U.S. Presidents—Abraham Lincoln, James A. Garfield, and William McKinley—each victims of assassination.\\r\\nIn the second shot, which runs just over eight seconds long, an assassin kneels feet of Lady Justice.
    1901Terrible Teddy, the Grizzly KingAmericanUnknownunknownhttps://en.wikipedia.org/wiki/Terrible_Teddy,_the_Grizzly_KingLasting just 61 seconds and consisting of two shots, the first shot is set in a wood during winter. The actor representing then vice-president Theodore Roosevelt enthusiastically hurries down a hillside towards a tree in the foreground. He falls once, but rights himself and cocks his rifle. Two other men, bearing signs reading \"His Photographer\" and \"His Press Agent\" respectively, follow him into the shot; the photographer sets up his camera. \"Teddy\" aims his rifle upward at the tree and fells what appears to be a common house cat, which he then proceeds to stab. \"Teddy\" holds his prize aloft, and the press agent takes notes. The second shot is taken in a slightly different part of the wood, on a path. \"Teddy\" rides the path on his horse towards the camera and out to the left of the shot, followed closely by the press agent and photographer, still dutifully holding their signs.
    1902Jack and the BeanstalkAmericanGeorge S. Fleming, Edwin S. Porterunknownhttps://en.wikipedia.org/wiki/Jack_and_the_Beanstalk_(1902_film)The earliest known adaptation of the classic fairytale, this films shows Jack trading his cow for the beans, his mother forcing him to drop them in the front yard, and beig forced upstairs. As he sleeps, Jack is visited by a fairy who shows him glimpses of what will await him when he ascends the bean stalk. In this version, Jack is the son of a deposed king. When Jack wakes up, he finds the beanstalk has grown and he climbs to the top where he enters the giant's home. The giant finds Jack, who narrowly escapes. The giant chases Jack down the bean stalk, but Jack is able to cut it down before the giant can get to safety. He falls and is killed as Jack celebrates. The fairy then reveals that Jack may return home as a prince.
    1903Alice in WonderlandAmericanCecil HepworthMay Clarkunknownhttps://en.wikipedia.org/wiki/Alice_in_Wonderland_(1903_film)Alice follows a large white rabbit down a \"Rabbit-hole\". She finds a tiny door. When she finds a bottle labeled \"Drink me\", she does, and shrinks, but not enough to pass through the door. She then eats something labeled \"Eat me\" and grows larger. She finds a fan when enables her to shrink enough to get into the \"Garden\" and try to get a \"Dog\" to play with her. She enters the \"White Rabbit's tiny House,\" but suddenly resumes her normal size. In order to get out, she has to use the \"magic fan.\"\\r\\nShe enters a kitchen, in which there is a cook and a woman holding a baby. She persuades the woman to give her the child and takes the infant outside after the cook starts throwing things around. The baby then turns into a pig and squirms out of her grip. \"The Duchess's Cheshire Cat\" appears and disappears a couple of times to Alice and directs her to the Mad Hatter's \"Mad Tea-Party.\" After a while, she leaves.\\r\\nThe Queen invites Alice to join the \"ROYAL PROCESSION\": a parade of marching playing cards and others headed by the White Rabbit. When Alice \"unintentionally offends the Queen\", the latter summons the \"Executioner\". Alice \"boxes the ears\", then flees when all the playing cards come for her. Then she wakes up and realizes it was all a dream.
    1903The Great Train RobberyAmericanEdwin S. Porterwesternhttps://en.wikipedia.org/wiki/The_Great_Train_Robbery_(1903_film)The film opens with two bandits breaking into a railroad telegraph office, where they force the operator at gunpoint to have a train stopped and to transmit orders for the engineer to fill the locomotive's tender at the station's water tank. They then knock the operator out and tie him up. As the train stops it is boarded by the bandits‍—‌now four. Two bandits enter an express car, kill a messenger and open a box of valuables with dynamite; the others kill the fireman and force the engineer to halt the train and disconnect the locomotive. The bandits then force the passengers off the train and rifle them for their belongings. One passenger tries to escape but is instantly shot down. Carrying their loot, the bandits escape in the locomotive, later stopping in a valley where their horses had been left.\\r\\nMeanwhile, back in the telegraph office, the bound operator awakens, but he collapses again. His daughter arrives bringing him his meal and cuts him free, and restores him to consciousness by dousing him with water.\\r\\nThere is some comic relief at a dance hall, where an Eastern stranger is forced to dance while the locals fire at his feet. The door suddenly opens and the telegraph operator rushes in to tell them of the robbery. The men quickly form a posse, which overtakes the bandits, and in a final shootout kills them all and recovers the stolen mail.
    1904The SuburbaniteAmericanWallace McCutcheoncomedyhttps://en.wikipedia.org/wiki/The_SuburbaniteThe film is about a family who move to the suburbs, hoping for a quiet life. Things start to go wrong, and the wife gets violent and starts throwing crockery, leading to her arrest.
    1905The Little Train RobberyAmericanEdwin Stanton Porterunknownhttps://en.wikipedia.org/wiki/The_Little_Train_RobberyThe opening scene shows the interior of the robbers' den. The walls are decorated with the portraits of notorious criminals and pictures illustrating the exploits of famous bandits. Some of the gang are lounging about, while others are reading novels and illustrated papers. Although of youthful appearance, each is dressed like a typical Western desperado. The \"Bandit Queen,\" leading a blindfolded new recruit, now enters the room. He is led to the center of the room, raises his right hand and is solemnly sworn in. When the bandage is removed from his eyes he finds himself looking into the muzzles of a dozen or more 45's. The gang then congratulates the new member and heartily shake his hand. The \"Bandit Queen\" who is evidently the leader of the gang, now calls for volunteers to hold up a train. All respond, but she picks out seven for the job who immediately leave the cabin.\\r\\nThe next scene shows the gang breaking into a barn. They steal ponies and ride away. Upon reaching the place agreed upon they picket their ponies and leaving them in charge of a trusted member proceed to a wild mountain spot in a bend of the railroad, where the road runs over a steep embankment. The spot is an ideal one for holding up a train. Cross ties are now placed on the railroad track and the gang hide in some bushes close by and wait for the train. The train soon approaches and is brought to a stop. The engineer leaves his engine and proceeds to remove the obstruction on the track. While he is bending over one of the gang sneaks up behind them and hits him on the head with an axe, and knocks him senseless down the embankment, while the gang surround the train and hold up the passengers. After securing all the \"valuables,\" consisting principally of candy and dolls, the robbers uncouple the engine and one car and make their escape just in time to avoid a posse of police who appear on the scene. Further up the road they abandon the engine and car, take to the woods and soon reach their ponies.\\r\\nIn the meantime the police have learned the particulars of the hold-up from the frightened passengers and have started up the railroad tracks after the fleeing robbers. The robbers are next seen riding up the bed of a shallow stream and finally reach their den, where the remainder of the gang have been waiting for them. Believing they have successfully eluded their pursuers, they proceed to divide the \"plunder.\" The police, however, have struck the right trail and are in close pursuit. While the \"plunder\" is being divided a sentry gives the alarm and the entire gang, abandoning everything, rush from the cabin barely in time to escape capture. The police make a hurried search and again start in pursuit. The robbers are so hard pressed that they are unable to reach their ponies, and are obliged to take chances on foot. The police now get in sight of the fleeing robbers and a lively chase follows through tall weeds, over a bridge and up a steep hill. Reaching a pond the police are close on their heels. The foremost robbers jump in clothes and all and strike out for the opposite bank. Two hesitate and are captured. Boats are secured and after an exciting tussle the entire gang is rounded up. In the mix up one of the police is dragged overboard. The final scene shows the entire gang of bedraggled and crestfallen robbers tied together with a rope and being led away by the police. Two of the police are loaded down with revolvers, knives and cartridge belts, and resemble walking aresenals. As a fitting climax a confederate steals out of the woods, cuts the rope and gallantly rescues the \"Bandit Queen.\"
    1905The Night Before ChristmasAmericanEdwin Stanton Porterunknownhttps://en.wikipedia.org/wiki/The_Night_Before_Christmas_(1905_film)Scenes are introduced using lines of the poem.[2] Santa Claus, played by Harry Eytinge, is shown feeding real reindeer[4] and finishes his work in the workshop. Meanwhile, the children of a city household hang their stockings and go to bed, but unable to sleep they engage in a pillow fight. Santa Claus leaves his home on a sleigh with his reindeer. He enters the children's house through the chimney, and leaves the presents. The children come down the stairs and enjoy their presents.
    1906Dream of a Rarebit FiendAmericanWallace McCutcheon and Edwin S. Portershorthttps://en.wikipedia.org/wiki/Dream_of_a_Rarebit_Fiend_(1906_film)The Rarebit Fiend gorges on Welsh rarebit at a restaurant. When he leaves, he begins to get dizzy as he starts to hallucinate. He desperately tries to hang onto a lamppost as the world spins all around him. A man helps him get home. He falls into bed and begins having more hallucinatory dreams. During a dream sequence, the furniture begins moving around the room. Imps emerge from a floating Welsh rarebit container and begin poking his head as he sleeps. His bed then begins dancing and spinning wildly around the room before flying out the window with the Fiend in it. The bed floats across the city as the Fiend floats up and off the bed. He hangs off the back and eventually gets caught on a weathervane atop a steeple. His bedclothes tear and he falls from the sky, crashing through his bedroom ceiling. The Fiend awakens from the dream after falling out of his bed.
    1906From Leadville to Aspen: A Hold-Up in the RockiesAmericanFrancis J. Marion and Wallace McCutcheonshort action/crime westernhttps://en.wikipedia.org/wiki/From_Leadville_to_Aspen:_A_Hold-Up_in_the_RockiesThe film features a train traveling through the Rockies and a hold up created by two thugs placing logs on the line. They systematically rob the wealthy occupants at gunpoint and then make their getaway along the tracks and later by a hi-jacked horse and cart.
    1906Kathleen MavourneenAmericanEdwin S. Portershort filmhttps://en.wikipedia.org/wiki/Kathleen_Mavourneen_(1906_film)Irish villager Kathleen is a tenant of Captain Clearfield, who controls local judges and criminals. Her father owes Clearfield a large debt. Terence O'More saves the village from Clearfield, causing a large celebration.\\r\\nFilm historian Charles Musser writes of Porter's adaptation, \"O'More not only rescues Kathleen from the villain but, through marriage, renews the family for another generation.\"[1]
    1907Daniel BooneAmericanWallace McCutcheon and Ediwin S. PorterWilliam Craven, Florence Lawrencebiographicalhttps://en.wikipedia.org/wiki/Daniel_Boone_(1907_film)Boone's daughter befriends an Indian maiden as Boone and his companion start out on a hunting expedition. While he is away, Boone's cabin is attacked by the Indians, who set it on fire and abduct Boone's daughter. Boone returns, swears vengeance, then heads out on the trail to the Indian camp. His daughter escapes but is chased. The Indians encounter Boone, which sets off a huge fight on the edge of a cliff. A burning arrow gets shot into the Indian camp. Boone gets tied to the stake and tortured. The burning arrow sets the Indian camp on fire, causing panic. Boone is rescued by his horse, and Boone has a knife fight in which he kills the Indian chief.[2]
    1907How Brown Saw the Baseball GameAmericanUnknownUnknowncomedyhttps://en.wikipedia.org/wiki/How_Brown_Saw_the_Baseball_GameBefore heading out to a baseball game at a nearby ballpark, sports fan Mr. Brown drinks several highball cocktails. He arrives at the ballpark to watch the game, but has become so inebriated that the game appears to him in reverse, with the players running the bases backwards and the baseball flying back into the pitcher's hand. After the game is over, Mr. Brown is escorted home by one of his friends. When they arrive at Brown's house, they encounter his wife who becomes furious with the friend and proceeds to physically assault him, believing he is responsible for her husband's severe intoxication.[1]
    1907Laughing GasAmericanEdwin Stanton PorterBertha Regustus, Edward Bouldencomedyhttps://en.wikipedia.org/wiki/Laughing_Gas_(film)#1907_FilmThe plot is that of a black woman going to the dentist for a toothache and being given laughing gas. On her way walking home, and in other situations, she can't stop laughing, and everyone she meets \"catches\" the laughter from her, including a vendor and police officers.
    1908The Adventures of DollieAmericanD. W. GriffithArthur V. Johnson, Linda Arvidsondramahttps://en.wikipedia.org/wiki/The_Adventures_of_DollieOn a beautiful summer day a father and mother take their daughter Dollie on an outing to the river. The mother refuses to buy a gypsy's wares. The gypsy tries to rob the mother, but the father drives him off. The gypsy returns to the camp and devises a plan. They return and kidnap Dollie while her parents are distracted. A rescue crew is organized, but the gypsy takes Dollie to his camp. They gag Dollie and hide her in a barrel before the rescue party gets to the camp. Once they leave the gypsies and escapes in their wagon. As the wagon crosses the river, the barrel falls into the water. Still sealed in the barrel, Dollie is swept downstream in dangerous currents. A boy who is fishing in the river finds the barrel, and Dollie is reunited safely with her parents.
    1908The Black ViperAmericanD. W. GriffithD. W. Griffithdramahttps://en.wikipedia.org/wiki/The_Black_ViperA thug accosts a girl as she leaves her workplace but a man rescues her. The thug vows revenge and, with the help of two friends, attacks the girl and her rescuer again as they're going for a walk. This time they succeed in kidnapping the rescuer. He is bound and gagged and taken away in a cart. The girl runs home and gets help from several neighbors. They track the ruffians down to a cabin in the mountains where the gang has trapped their victim and set the cabin on fire. A thug and Rescuer fight on the roof of the house.
    1908A Calamitous ElopementAmericanD.W. GriffithHarry Solter, Linda Arvidsoncomedyhttps://en.wikipedia.org/wiki/A_Calamitous_ElopementA young couple decides to elope after being caught in the midst of a romantic moment by the woman's angry father. They make plans to leave, but a thief discovers their plans and hides in their trunk and waits for the right moment to steal their belongings.
    1908The Call of the WildAmericanD. W. GriffithCharles Insleeadventurehttps://en.wikipedia.org/wiki/The_Call_of_the_Wild_(1908_film)A white girl (Florence Lawrence) rejects a proposal from an Indian brave (Charles Inslee) in this early one-reel Western melodrama. Despite the rejection, the Indian still comes to the girl's defense when she is abducted by his warring tribe. In her first year in films, Florence Lawrence was already the most popular among the Biograph Company's anonymous stock company players. By 1909, she was known the world over as \"The Biograph Girl.\"
    1908A Christmas CarolAmericanUnknownTom Rickettsdramahttps://en.wikipedia.org/wiki/A_Christmas_Carol_(1908_film)No prints of the first American film adaptation of A Christmas Carol are known to exist,[1] but The Moving Picture World magazine provided a scene-by-scene description before the film's release.[2] Scrooge goes into his office and begins working. His nephew, along with three women who wish for Scrooge to donate enter. However, Scrooge dismisses them. On the night of Christmas Eve, his long-dead partner Jacob Marley comes as a ghost, warning him of a horrible fate if he does not change his ways. Scrooge meets three spirits that show Scrooge the real meaning of Christmas, along with his grave, the result of his parsimonious ways. The next morning, he wakes and realizes the error of his ways. Scrooge was then euphoric and generous for the rest of his life.
    1908The Fight for FreedomAmericanD. W. GriffithFlorence Auer, John G. Adolfiwesternhttps://en.wikipedia.org/wiki/The_Fight_for_FreedomThe film opens in a town on the Mexican border. A poker game is going on in the local saloon. One of the players cheats and is shot dead by another of the players, a Mexican named Pedro. In the uproar that follows Pedro is wounded as he escapes from the saloon. The sheriff is called, who tracks Pedro to his home but Pedro kills the sherriff too. While Pedro hides, his wife Juanita, is arrested on suspicion of murdering the sheriff. Pedro rescues her from the town jail and the two head for the Mexican border. Caught by the posse before they reach the border, Juanita is killed and the film ends with Pedro being arrested and taken back to town.
    " + }, + "text": "\n\n\n1901\nKansas Saloon Smashers\nAmerican\nUnknown\n\nunknown\nhttps://en.wikipedia.org/wiki/Kansas_Saloon_Smashers\nA bartender is working at a saloon, serving drinks to customers. After he fills a stereotypically Irish man's bucket with beer, Carrie Nation and her followers burst inside. They assault the Irish man, pulling his hat over his eyes and then dumping the beer over his head. The group then begin wrecking the bar, smashing the fixtures, mirrors, and breaking the cash register. The bartender then sprays seltzer water in Nation's face before a group of policemen appear and order everybody to leave.[1]\n\n\n1901\nLove by the Light of the Moon\nAmerican\nUnknown\n\nunknown\nhttps://en.wikipedia.org/wiki/Love_by_the_Light_of_the_Moon\nThe moon, painted with a smiling face hangs over a park at night. A young couple walking past a fence learn on a railing and look up. The moon smiles. They embrace, and the moon's smile gets bigger. They then sit down on a bench by a tree. The moon's view is blocked, causing him to frown. In the last scene, the man fans the woman with his hat because the moon has left the sky and is perched over her shoulder to see everything better.\n\n\n1901\nThe Martyred Presidents\nAmerican\nUnknown\n\nunknown\nhttps://en.wikipedia.org/wiki/The_Martyred_Presidents\nThe film, just over a minute long, is composed of two shots. In the first, a girl sits at the base of an altar or tomb, her face hidden from the camera. At the center of the altar, a viewing portal displays the portraits of three U.S. Presidents—Abraham Lincoln, James A. Garfield, and William McKinley—each victims of assassination.\\r\\nIn the second shot, which runs just over eight seconds long, an assassin kneels feet of Lady Justice.\n\n\n1901\nTerrible Teddy, the Grizzly King\nAmerican\nUnknown\n\nunknown\nhttps://en.wikipedia.org/wiki/Terrible_Teddy,_the_Grizzly_King\nLasting just 61 seconds and consisting of two shots, the first shot is set in a wood during winter. The actor representing then vice-president Theodore Roosevelt enthusiastically hurries down a hillside towards a tree in the foreground. He falls once, but rights himself and cocks his rifle. Two other men, bearing signs reading \"His Photographer\" and \"His Press Agent\" respectively, follow him into the shot; the photographer sets up his camera. \"Teddy\" aims his rifle upward at the tree and fells what appears to be a common house cat, which he then proceeds to stab. \"Teddy\" holds his prize aloft, and the press agent takes notes. The second shot is taken in a slightly different part of the wood, on a path. \"Teddy\" rides the path on his horse towards the camera and out to the left of the shot, followed closely by the press agent and photographer, still dutifully holding their signs.\n\n\n1902\nJack and the Beanstalk\nAmerican\nGeorge S. Fleming, Edwin S. Porter\n\nunknown\nhttps://en.wikipedia.org/wiki/Jack_and_the_Beanstalk_(1902_film)\nThe earliest known adaptation of the classic fairytale, this films shows Jack trading his cow for the beans, his mother forcing him to drop them in the front yard, and beig forced upstairs. As he sleeps, Jack is visited by a fairy who shows him glimpses of what will await him when he ascends the bean stalk. In this version, Jack is the son of a deposed king. When Jack wakes up, he finds the beanstalk has grown and he climbs to the top where he enters the giant's home. The giant finds Jack, who narrowly escapes. The giant chases Jack down the bean stalk, but Jack is able to cut it down before the giant can get to safety. He falls and is killed as Jack celebrates. The fairy then reveals that Jack may return home as a prince.\n\n\n1903\nAlice in Wonderland\nAmerican\nCecil Hepworth\nMay Clark\nunknown\nhttps://en.wikipedia.org/wiki/Alice_in_Wonderland_(1903_film)\nAlice follows a large white rabbit down a \"Rabbit-hole\". She finds a tiny door. When she finds a bottle labeled \"Drink me\", she does, and shrinks, but not enough to pass through the door. She then eats something labeled \"Eat me\" and grows larger. She finds a fan when enables her to shrink enough to get into the \"Garden\" and try to get a \"Dog\" to play with her. She enters the \"White Rabbit's tiny House,\" but suddenly resumes her normal size. In order to get out, she has to use the \"magic fan.\"\\r\\nShe enters a kitchen, in which there is a cook and a woman holding a baby. She persuades the woman to give her the child and takes the infant outside after the cook starts throwing things around. The baby then turns into a pig and squirms out of her grip. \"The Duchess's Cheshire Cat\" appears and disappears a couple of times to Alice and directs her to the Mad Hatter's \"Mad Tea-Party.\" After a while, she leaves.\\r\\nThe Queen invites Alice to join the \"ROYAL PROCESSION\": a parade of marching playing cards and others headed by the White Rabbit. When Alice \"unintentionally offends the Queen\", the latter summons the \"Executioner\". Alice \"boxes the ears\", then flees when all the playing cards come for her. Then she wakes up and realizes it was all a dream.\n\n\n1903\nThe Great Train Robbery\nAmerican\nEdwin S. Porter\n\nwestern\nhttps://en.wikipedia.org/wiki/The_Great_Train_Robbery_(1903_film)\nThe film opens with two bandits breaking into a railroad telegraph office, where they force the operator at gunpoint to have a train stopped and to transmit orders for the engineer to fill the locomotive's tender at the station's water tank. They then knock the operator out and tie him up. As the train stops it is boarded by the bandits‍—‌now four. Two bandits enter an express car, kill a messenger and open a box of valuables with dynamite; the others kill the fireman and force the engineer to halt the train and disconnect the locomotive. The bandits then force the passengers off the train and rifle them for their belongings. One passenger tries to escape but is instantly shot down. Carrying their loot, the bandits escape in the locomotive, later stopping in a valley where their horses had been left.\\r\\nMeanwhile, back in the telegraph office, the bound operator awakens, but he collapses again. His daughter arrives bringing him his meal and cuts him free, and restores him to consciousness by dousing him with water.\\r\\nThere is some comic relief at a dance hall, where an Eastern stranger is forced to dance while the locals fire at his feet. The door suddenly opens and the telegraph operator rushes in to tell them of the robbery. The men quickly form a posse, which overtakes the bandits, and in a final shootout kills them all and recovers the stolen mail.\n\n\n1904\nThe Suburbanite\nAmerican\nWallace McCutcheon\n\ncomedy\nhttps://en.wikipedia.org/wiki/The_Suburbanite\nThe film is about a family who move to the suburbs, hoping for a quiet life. Things start to go wrong, and the wife gets violent and starts throwing crockery, leading to her arrest.\n\n\n1905\nThe Little Train Robbery\nAmerican\nEdwin Stanton Porter\n\nunknown\nhttps://en.wikipedia.org/wiki/The_Little_Train_Robbery\nThe opening scene shows the interior of the robbers' den. The walls are decorated with the portraits of notorious criminals and pictures illustrating the exploits of famous bandits. Some of the gang are lounging about, while others are reading novels and illustrated papers. Although of youthful appearance, each is dressed like a typical Western desperado. The \"Bandit Queen,\" leading a blindfolded new recruit, now enters the room. He is led to the center of the room, raises his right hand and is solemnly sworn in. When the bandage is removed from his eyes he finds himself looking into the muzzles of a dozen or more 45's. The gang then congratulates the new member and heartily shake his hand. The \"Bandit Queen\" who is evidently the leader of the gang, now calls for volunteers to hold up a train. All respond, but she picks out seven for the job who immediately leave the cabin.\\r\\nThe next scene shows the gang breaking into a barn. They steal ponies and ride away. Upon reaching the place agreed upon they picket their ponies and leaving them in charge of a trusted member proceed to a wild mountain spot in a bend of the railroad, where the road runs over a steep embankment. The spot is an ideal one for holding up a train. Cross ties are now placed on the railroad track and the gang hide in some bushes close by and wait for the train. The train soon approaches and is brought to a stop. The engineer leaves his engine and proceeds to remove the obstruction on the track. While he is bending over one of the gang sneaks up behind them and hits him on the head with an axe, and knocks him senseless down the embankment, while the gang surround the train and hold up the passengers. After securing all the \"valuables,\" consisting principally of candy and dolls, the robbers uncouple the engine and one car and make their escape just in time to avoid a posse of police who appear on the scene. Further up the road they abandon the engine and car, take to the woods and soon reach their ponies.\\r\\nIn the meantime the police have learned the particulars of the hold-up from the frightened passengers and have started up the railroad tracks after the fleeing robbers. The robbers are next seen riding up the bed of a shallow stream and finally reach their den, where the remainder of the gang have been waiting for them. Believing they have successfully eluded their pursuers, they proceed to divide the \"plunder.\" The police, however, have struck the right trail and are in close pursuit. While the \"plunder\" is being divided a sentry gives the alarm and the entire gang, abandoning everything, rush from the cabin barely in time to escape capture. The police make a hurried search and again start in pursuit. The robbers are so hard pressed that they are unable to reach their ponies, and are obliged to take chances on foot. The police now get in sight of the fleeing robbers and a lively chase follows through tall weeds, over a bridge and up a steep hill. Reaching a pond the police are close on their heels. The foremost robbers jump in clothes and all and strike out for the opposite bank. Two hesitate and are captured. Boats are secured and after an exciting tussle the entire gang is rounded up. In the mix up one of the police is dragged overboard. The final scene shows the entire gang of bedraggled and crestfallen robbers tied together with a rope and being led away by the police. Two of the police are loaded down with revolvers, knives and cartridge belts, and resemble walking aresenals. As a fitting climax a confederate steals out of the woods, cuts the rope and gallantly rescues the \"Bandit Queen.\"\n\n\n1905\nThe Night Before Christmas\nAmerican\nEdwin Stanton Porter\n\nunknown\nhttps://en.wikipedia.org/wiki/The_Night_Before_Christmas_(1905_film)\nScenes are introduced using lines of the poem.[2] Santa Claus, played by Harry Eytinge, is shown feeding real reindeer[4] and finishes his work in the workshop. Meanwhile, the children of a city household hang their stockings and go to bed, but unable to sleep they engage in a pillow fight. Santa Claus leaves his home on a sleigh with his reindeer. He enters the children's house through the chimney, and leaves the presents. The children come down the stairs and enjoy their presents.\n\n\n1906\nDream of a Rarebit Fiend\nAmerican\nWallace McCutcheon and Edwin S. Porter\n\nshort\nhttps://en.wikipedia.org/wiki/Dream_of_a_Rarebit_Fiend_(1906_film)\nThe Rarebit Fiend gorges on Welsh rarebit at a restaurant. When he leaves, he begins to get dizzy as he starts to hallucinate. He desperately tries to hang onto a lamppost as the world spins all around him. A man helps him get home. He falls into bed and begins having more hallucinatory dreams. During a dream sequence, the furniture begins moving around the room. Imps emerge from a floating Welsh rarebit container and begin poking his head as he sleeps. His bed then begins dancing and spinning wildly around the room before flying out the window with the Fiend in it. The bed floats across the city as the Fiend floats up and off the bed. He hangs off the back and eventually gets caught on a weathervane atop a steeple. His bedclothes tear and he falls from the sky, crashing through his bedroom ceiling. The Fiend awakens from the dream after falling out of his bed.\n\n\n1906\nFrom Leadville to Aspen: A Hold-Up in the Rockies\nAmerican\nFrancis J. Marion and Wallace McCutcheon\n\nshort action/crime western\nhttps://en.wikipedia.org/wiki/From_Leadville_to_Aspen:_A_Hold-Up_in_the_Rockies\nThe film features a train traveling through the Rockies and a hold up created by two thugs placing logs on the line. They systematically rob the wealthy occupants at gunpoint and then make their getaway along the tracks and later by a hi-jacked horse and cart.\n\n\n1906\nKathleen Mavourneen\nAmerican\nEdwin S. Porter\n\nshort film\nhttps://en.wikipedia.org/wiki/Kathleen_Mavourneen_(1906_film)\nIrish villager Kathleen is a tenant of Captain Clearfield, who controls local judges and criminals. Her father owes Clearfield a large debt. Terence O'More saves the village from Clearfield, causing a large celebration.\\r\\nFilm historian Charles Musser writes of Porter's adaptation, \"O'More not only rescues Kathleen from the villain but, through marriage, renews the family for another generation.\"[1]\n\n\n1907\nDaniel Boone\nAmerican\nWallace McCutcheon and Ediwin S. Porter\nWilliam Craven, Florence Lawrence\nbiographical\nhttps://en.wikipedia.org/wiki/Daniel_Boone_(1907_film)\nBoone's daughter befriends an Indian maiden as Boone and his companion start out on a hunting expedition. While he is away, Boone's cabin is attacked by the Indians, who set it on fire and abduct Boone's daughter. Boone returns, swears vengeance, then heads out on the trail to the Indian camp. His daughter escapes but is chased. The Indians encounter Boone, which sets off a huge fight on the edge of a cliff. A burning arrow gets shot into the Indian camp. Boone gets tied to the stake and tortured. The burning arrow sets the Indian camp on fire, causing panic. Boone is rescued by his horse, and Boone has a knife fight in which he kills the Indian chief.[2]\n\n\n1907\nHow Brown Saw the Baseball Game\nAmerican\nUnknown\nUnknown\ncomedy\nhttps://en.wikipedia.org/wiki/How_Brown_Saw_the_Baseball_Game\nBefore heading out to a baseball game at a nearby ballpark, sports fan Mr. Brown drinks several highball cocktails. He arrives at the ballpark to watch the game, but has become so inebriated that the game appears to him in reverse, with the players running the bases backwards and the baseball flying back into the pitcher's hand. After the game is over, Mr. Brown is escorted home by one of his friends. When they arrive at Brown's house, they encounter his wife who becomes furious with the friend and proceeds to physically assault him, believing he is responsible for her husband's severe intoxication.[1]\n\n\n1907\nLaughing Gas\nAmerican\nEdwin Stanton Porter\nBertha Regustus, Edward Boulden\ncomedy\nhttps://en.wikipedia.org/wiki/Laughing_Gas_(film)#1907_Film\nThe plot is that of a black woman going to the dentist for a toothache and being given laughing gas. On her way walking home, and in other situations, she can't stop laughing, and everyone she meets \"catches\" the laughter from her, including a vendor and police officers.\n\n\n1908\nThe Adventures of Dollie\nAmerican\nD. W. Griffith\nArthur V. Johnson, Linda Arvidson\ndrama\nhttps://en.wikipedia.org/wiki/The_Adventures_of_Dollie\nOn a beautiful summer day a father and mother take their daughter Dollie on an outing to the river. The mother refuses to buy a gypsy's wares. The gypsy tries to rob the mother, but the father drives him off. The gypsy returns to the camp and devises a plan. They return and kidnap Dollie while her parents are distracted. A rescue crew is organized, but the gypsy takes Dollie to his camp. They gag Dollie and hide her in a barrel before the rescue party gets to the camp. Once they leave the gypsies and escapes in their wagon. As the wagon crosses the river, the barrel falls into the water. Still sealed in the barrel, Dollie is swept downstream in dangerous currents. A boy who is fishing in the river finds the barrel, and Dollie is reunited safely with her parents.\n\n\n1908\nThe Black Viper\nAmerican\nD. W. Griffith\nD. W. Griffith\ndrama\nhttps://en.wikipedia.org/wiki/The_Black_Viper\nA thug accosts a girl as she leaves her workplace but a man rescues her. The thug vows revenge and, with the help of two friends, attacks the girl and her rescuer again as they're going for a walk. This time they succeed in kidnapping the rescuer. He is bound and gagged and taken away in a cart. The girl runs home and gets help from several neighbors. They track the ruffians down to a cabin in the mountains where the gang has trapped their victim and set the cabin on fire. A thug and Rescuer fight on the roof of the house.\n\n\n1908\nA Calamitous Elopement\nAmerican\nD.W. Griffith\nHarry Solter, Linda Arvidson\ncomedy\nhttps://en.wikipedia.org/wiki/A_Calamitous_Elopement\nA young couple decides to elope after being caught in the midst of a romantic moment by the woman's angry father. They make plans to leave, but a thief discovers their plans and hides in their trunk and waits for the right moment to steal their belongings.\n\n\n1908\nThe Call of the Wild\nAmerican\nD. W. Griffith\nCharles Inslee\nadventure\nhttps://en.wikipedia.org/wiki/The_Call_of_the_Wild_(1908_film)\nA white girl (Florence Lawrence) rejects a proposal from an Indian brave (Charles Inslee) in this early one-reel Western melodrama. Despite the rejection, the Indian still comes to the girl's defense when she is abducted by his warring tribe. In her first year in films, Florence Lawrence was already the most popular among the Biograph Company's anonymous stock company players. By 1909, she was known the world over as \"The Biograph Girl.\"\n\n\n1908\nA Christmas Carol\nAmerican\nUnknown\nTom Ricketts\ndrama\nhttps://en.wikipedia.org/wiki/A_Christmas_Carol_(1908_film)\nNo prints of the first American film adaptation of A Christmas Carol are known to exist,[1] but The Moving Picture World magazine provided a scene-by-scene description before the film's release.[2] Scrooge goes into his office and begins working. His nephew, along with three women who wish for Scrooge to donate enter. However, Scrooge dismisses them. On the night of Christmas Eve, his long-dead partner Jacob Marley comes as a ghost, warning him of a horrible fate if he does not change his ways. Scrooge meets three spirits that show Scrooge the real meaning of Christmas, along with his grave, the result of his parsimonious ways. The next morning, he wakes and realizes the error of his ways. Scrooge was then euphoric and generous for the rest of his life.\n\n\n1908\nThe Fight for Freedom\nAmerican\nD. W. Griffith\nFlorence Auer, John G. Adolfi\nwestern\nhttps://en.wikipedia.org/wiki/The_Fight_for_Freedom\nThe film opens in a town on the Mexican border. A poker game is going on in the local saloon. One of the players cheats and is shot dead by another of the players, a Mexican named Pedro. In the uproar that follows Pedro is wounded as he escapes from the saloon. The sheriff is called, who tracks Pedro to his home but Pedro kills the sherriff too. While Pedro hides, his wife Juanita, is arrested on suspicion of murdering the sheriff. Pedro rescues her from the town jail and the two head for the Mexican border. Caught by the posse before they reach the border, Juanita is killed and the film ends with Pedro being arrested and taken back to town.\n\n\n" + } +] \ No newline at end of file diff --git a/test_unstructured_ingest/test-ingest-s3-minio.sh b/test_unstructured_ingest/test-ingest-s3-minio.sh new file mode 100755 index 0000000000..000c28e28b --- /dev/null +++ b/test_unstructured_ingest/test-ingest-s3-minio.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + +set -e + + +SCRIPT_DIR=$(dirname "$(realpath "$0")") +cd "$SCRIPT_DIR"/.. || exit 1 +OUTPUT_FOLDER_NAME=s3-minio +OUTPUT_DIR=$SCRIPT_DIR/structured-output/$OUTPUT_FOLDER_NAME +DOWNLOAD_DIR=$SCRIPT_DIR/download/$OUTPUT_FOLDER_NAME +max_processes=${MAX_PROCESSES:=$(python3 -c "import os; print(os.cpu_count())")} +secret_key=minioadmin +access_key=minioadmin + +# shellcheck disable=SC1091 +source "$SCRIPT_DIR"/cleanup.sh + +function cleanup() { + # Kill the container so the script can be repeatedly run using the same ports + echo "Stopping Minio Docker container" + docker-compose -f scripts/minio-test-helpers/docker-compose.yaml down --remove-orphans -v + + cleanup_dir "$OUTPUT_DIR" +} + +trap cleanup EXIT + +# shellcheck source=/dev/null +scripts/minio-test-helpers/create-and-check-minio.sh +wait + +AWS_SECRET_ACCESS_KEY=$secret_key AWS_ACCESS_KEY_ID=$access_key PYTHONPATH=. ./unstructured/ingest/main.py \ + s3 \ + --num-processes "$max_processes" \ + --download-dir "$DOWNLOAD_DIR" \ + --metadata-exclude coordinates,filename,file_directory,metadata.data_source.date_processed,metadata.data_source.date_modified,metadata.last_modified,metadata.detection_class_prob,metadata.parent_id,metadata.category_depth \ + --strategy hi_res \ + --preserve-downloads \ + --reprocess \ + --output-dir "$OUTPUT_DIR" \ + --verbose \ + --remote-url s3://utic-dev-tech-fixtures/ \ + --endpoint-url http://localhost:9000 + + +"$SCRIPT_DIR"/check-diff-expected-output.sh $OUTPUT_FOLDER_NAME diff --git a/test_unstructured_ingest/test-ingest.sh b/test_unstructured_ingest/test-ingest.sh index 56568b37f0..8c2dffc977 100755 --- a/test_unstructured_ingest/test-ingest.sh +++ b/test_unstructured_ingest/test-ingest.sh @@ -10,6 +10,7 @@ export OMP_THREAD_LIMIT=1 scripts=( 'test-ingest-s3.sh' +'test-ingest-s3-minio.sh' 'test-ingest-azure.sh' 'test-ingest-biomed-api.sh' 'test-ingest-biomed-path.sh' diff --git a/unstructured/__version__.py b/unstructured/__version__.py index a4cf981717..acf12be0ae 100644 --- a/unstructured/__version__.py +++ b/unstructured/__version__.py @@ -1 +1 @@ -__version__ = "0.10.19-dev7" # pragma: no cover +__version__ = "0.10.19-dev8" # pragma: no cover diff --git a/unstructured/ingest/cli/cmds/s3.py b/unstructured/ingest/cli/cmds/s3.py index 88c46fdb25..34a7845f1b 100644 --- a/unstructured/ingest/cli/cmds/s3.py +++ b/unstructured/ingest/cli/cmds/s3.py @@ -1,4 +1,5 @@ import logging +import typing as t from dataclasses import dataclass import click @@ -22,6 +23,7 @@ @dataclass class S3CliConfig(BaseConfig, CliMixin): anonymous: bool = False + endpoint_url: t.Optional[str] = None @staticmethod def add_cli_options(cmd: click.Command) -> None: @@ -32,6 +34,13 @@ def add_cli_options(cmd: click.Command) -> None: default=False, help="Connect to s3 without local AWS credentials.", ), + click.Option( + ["--endpoint-url"], + type=str, + default=None, + help="Use this endpoint_url, if specified. Needed for " + "connecting to non-AWS S3 buckets.", + ), ] cmd.params.extend(options) diff --git a/unstructured/ingest/runner/s3.py b/unstructured/ingest/runner/s3.py index 292270e50a..e3646305fa 100644 --- a/unstructured/ingest/runner/s3.py +++ b/unstructured/ingest/runner/s3.py @@ -15,6 +15,7 @@ def s3( verbose: bool = False, recursive: bool = False, anonymous: bool = False, + endpoint_url: t.Optional[str] = None, writer_type: t.Optional[str] = None, writer_kwargs: t.Optional[dict] = None, **kwargs, @@ -31,11 +32,14 @@ def s3( from unstructured.ingest.connector.s3 import S3SourceConnector, SimpleS3Config + access_kwargs: t.Dict[str, t.Any] = {"anon": anonymous} + if endpoint_url: + access_kwargs["endpoint_url"] = endpoint_url source_doc_connector = S3SourceConnector( # type: ignore connector_config=SimpleS3Config( path=remote_url, recursive=recursive, - access_kwargs={"anon": anonymous}, + access_kwargs=access_kwargs, ), read_config=read_config, partition_config=partition_config, diff --git a/unstructured/ingest/runner/writers.py b/unstructured/ingest/runner/writers.py index 46a875035e..7be5073c0f 100644 --- a/unstructured/ingest/runner/writers.py +++ b/unstructured/ingest/runner/writers.py @@ -9,6 +9,7 @@ def s3_writer( remote_url: str, anonymous: bool, + endpoint_url: t.Optional[str] = None, verbose: bool = False, **kwargs, ): @@ -17,11 +18,15 @@ def s3_writer( SimpleS3Config, ) + access_kwargs: t.Dict[str, t.Any] = {"anon": anonymous} + if endpoint_url: + access_kwargs["endpoint_url"] = endpoint_url + return S3DestinationConnector( write_config=WriteConfig(), connector_config=SimpleS3Config( path=remote_url, - access_kwargs={"anon": anonymous}, + access_kwargs=access_kwargs, ), ) From 13453d63587347e6f185cb4bfd32eeb8e6fda7aa Mon Sep 17 00:00:00 2001 From: Manirevuri <113291632+Manirevuri@users.noreply.github.com> Date: Tue, 3 Oct 2023 14:42:32 -0600 Subject: [PATCH 5/5] Fix: Documentation for Unstructured API's (#1624) Fixed "files=file_data" param for all python files --------- Co-authored-by: Austin Walker --- CHANGELOG.md | 2 +- docs/source/api.rst | 20 ++++++++++---------- unstructured/__version__.py | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23878a7c2f..09437d9523 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 0.10.19-dev8 +## 0.10.19-dev9 ### Enhancements diff --git a/docs/source/api.rst b/docs/source/api.rst index 7ade12ab32..3d682940d8 100644 --- a/docs/source/api.rst +++ b/docs/source/api.rst @@ -108,7 +108,7 @@ When elements are extracted from PDFs or images, it may be useful to get their b file_path = "/Path/To/File" file_data = {'files': open(file_path, 'rb')} - response = requests.post(url, headers=headers, files=files, data=data) + response = requests.post(url, headers=headers, files=file_data, data=data) file_data['files'].close() @@ -155,7 +155,7 @@ You can specify the encoding to use to decode the text input. If no value is pro file_path = "/Path/To/File" file_data = {'files': open(file_path, 'rb')} - response = requests.post(url, headers=headers, files=files, data=data) + response = requests.post(url, headers=headers, files=file_data, data=data) file_data['files'].close() @@ -204,7 +204,7 @@ You can also specify what languages to use for OCR with the ``ocr_languages`` kw file_path = "/Path/To/File" file_data = {'files': open(file_path, 'rb')} - response = requests.post(url, headers=headers, files=files, data=data) + response = requests.post(url, headers=headers, files=file_data, data=data) file_data['files'].close() @@ -250,7 +250,7 @@ By default the result will be in ``json``, but it can be set to ``text/csv`` to file_path = "/Path/To/File" file_data = {'files': open(file_path, 'rb')} - response = requests.post(url, headers=headers, files=files, data=data) + response = requests.post(url, headers=headers, files=file_data, data=data) file_data['files'].close() @@ -296,7 +296,7 @@ Pass the `include_page_breaks` parameter to `true` to include `PageBreak` elemen file_path = "/Path/To/File" file_data = {'files': open(file_path, 'rb')} - response = requests.post(url, headers=headers, files=files, data=data) + response = requests.post(url, headers=headers, files=file_data, data=data) file_data['files'].close() @@ -345,7 +345,7 @@ On the other hand, ``hi_res`` is the better choice for PDFs that may have text w file_path = "/Path/To/File" file_data = {'files': open(file_path, 'rb')} - response = requests.post(url, headers=headers, files=files, data=data) + response = requests.post(url, headers=headers, files=file_data, data=data) file_data['files'].close() @@ -398,7 +398,7 @@ To use the ``hi_res`` strategy with **Chipper** model, pass the argument for ``h file_path = "/Path/To/File" file_data = {'files': open(file_path, 'rb')} - response = requests.post(url, headers=headers, files=files, data=data) + response = requests.post(url, headers=headers, files=file_data, data=data) file_data['files'].close() @@ -451,7 +451,7 @@ To extract the table structure from PDF files using the ``hi_res`` strategy, ens file_path = "/Path/To/File" file_data = {'files': open(file_path, 'rb')} - response = requests.post(url, headers=headers, files=files, data=data) + response = requests.post(url, headers=headers, files=file_data, data=data) file_data['files'].close() @@ -499,7 +499,7 @@ We also provide support for enabling and disabling table extraction for file typ file_path = "/Path/To/File" file_data = {'files': open(file_path, 'rb')} - response = requests.post(url, headers=headers, files=files, data=data) + response = requests.post(url, headers=headers, files=file_data, data=data) file_data['files'].close() @@ -545,7 +545,7 @@ When processing XML documents, set the ``xml_keep_tags`` parameter to ``true`` t file_path = "/Path/To/File" file_data = {'files': open(file_path, 'rb')} - response = requests.post(url, headers=headers, files=files, data=data) + response = requests.post(url, headers=headers, files=file_data, data=data) file_data['files'].close() diff --git a/unstructured/__version__.py b/unstructured/__version__.py index acf12be0ae..d71d465e92 100644 --- a/unstructured/__version__.py +++ b/unstructured/__version__.py @@ -1 +1 @@ -__version__ = "0.10.19-dev8" # pragma: no cover +__version__ = "0.10.19-dev9" # pragma: no cover