From b5dcea2afe9857ec02257e7c2b2aaa5bf3b12797 Mon Sep 17 00:00:00 2001 From: Nick Burch Date: Wed, 19 Sep 2007 11:56:36 +0000 Subject: [PATCH] Move POIDocument out of the scratchpad git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@577259 13f79535-47bb-0310-9956-ffa450edef68 --- build.xml | 4 + src/java/org/apache/poi/POIDocument.java | 216 ++++++++++++++++++ .../org/apache/poi/TestPOIDocument.java | 117 ++++++++++ .../poi/hslf/data/basic_test_ppt_file.ppt | Bin 0 -> 15360 bytes .../org/apache/poi/hwpf/data/test2.doc | Bin 0 -> 19968 bytes 5 files changed, 337 insertions(+) create mode 100644 src/java/org/apache/poi/POIDocument.java create mode 100644 src/testcases/org/apache/poi/TestPOIDocument.java create mode 100644 src/testcases/org/apache/poi/hslf/data/basic_test_ppt_file.ppt create mode 100755 src/testcases/org/apache/poi/hwpf/data/test2.doc diff --git a/build.xml b/build.xml index ce1cf7ff4..4dac62c70 100644 --- a/build.xml +++ b/build.xml @@ -387,6 +387,10 @@ under the License. + + diff --git a/src/java/org/apache/poi/POIDocument.java b/src/java/org/apache/poi/POIDocument.java new file mode 100644 index 000000000..bc3b78dd2 --- /dev/null +++ b/src/java/org/apache/poi/POIDocument.java @@ -0,0 +1,216 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Iterator; +import java.util.List; + +import org.apache.poi.hpsf.DocumentSummaryInformation; +import org.apache.poi.hpsf.MutablePropertySet; +import org.apache.poi.hpsf.PropertySet; +import org.apache.poi.hpsf.PropertySetFactory; +import org.apache.poi.hpsf.SummaryInformation; +import org.apache.poi.poifs.filesystem.DirectoryEntry; +import org.apache.poi.poifs.filesystem.DocumentEntry; +import org.apache.poi.poifs.filesystem.DocumentInputStream; +import org.apache.poi.poifs.filesystem.Entry; +import org.apache.poi.poifs.filesystem.POIFSFileSystem; +import org.apache.poi.util.POILogFactory; +import org.apache.poi.util.POILogger; + +/** + * This holds the common functionality for all POI + * Document classes. + * Currently, this relates to Document Information Properties + * + * @author Nick Burch + */ +public abstract class POIDocument { + /** Holds metadata on our document */ + protected SummaryInformation sInf; + /** Holds further metadata on our document */ + protected DocumentSummaryInformation dsInf; + /** The open POIFS FileSystem that contains our document */ + protected POIFSFileSystem filesystem; + + /** For our own logging use */ + protected POILogger logger = POILogFactory.getLogger(this.getClass()); + + + /** + * Fetch the Document Summary Information of the document + */ + public DocumentSummaryInformation getDocumentSummaryInformation() { return dsInf; } + + /** + * Fetch the Summary Information of the document + */ + public SummaryInformation getSummaryInformation() { return sInf; } + + /** + * Find, and create objects for, the standard + * Documment Information Properties (HPSF) + */ + protected void readProperties() { + // DocumentSummaryInformation + dsInf = (DocumentSummaryInformation)getPropertySet(DocumentSummaryInformation.DEFAULT_STREAM_NAME); + + // SummaryInformation + sInf = (SummaryInformation)getPropertySet(SummaryInformation.DEFAULT_STREAM_NAME); + } + + /** + * For a given named property entry, either return it or null if + * if it wasn't found + */ + protected PropertySet getPropertySet(String setName) { + DocumentInputStream dis; + try { + // Find the entry, and get an input stream for it + dis = filesystem.createDocumentInputStream(setName); + } catch(IOException ie) { + // Oh well, doesn't exist + System.err.println("Error getting property set with name " + setName + "\n" + ie); + return null; + } + + try { + // Create the Property Set + PropertySet set = PropertySetFactory.create(dis); + return set; + } catch(IOException ie) { + // Must be corrupt or something like that + System.err.println("Error creating property set with name " + setName + "\n" + ie); + } catch(org.apache.poi.hpsf.HPSFException he) { + // Oh well, doesn't exist + System.err.println("Error creating property set with name " + setName + "\n" + he); + } + return null; + } + + /** + * Writes out the standard Documment Information Properties (HPSF) + * @param outFS the POIFSFileSystem to write the properties into + */ + protected void writeProperties(POIFSFileSystem outFS) throws IOException { + writeProperties(outFS, null); + } + /** + * Writes out the standard Documment Information Properties (HPSF) + * @param outFS the POIFSFileSystem to write the properties into + * @param writtenEntries a list of POIFS entries to add the property names too + */ + protected void writeProperties(POIFSFileSystem outFS, List writtenEntries) throws IOException { + if(sInf != null) { + writePropertySet(SummaryInformation.DEFAULT_STREAM_NAME,sInf,outFS); + if(writtenEntries != null) { + writtenEntries.add(SummaryInformation.DEFAULT_STREAM_NAME); + } + } + if(dsInf != null) { + writePropertySet(DocumentSummaryInformation.DEFAULT_STREAM_NAME,dsInf,outFS); + if(writtenEntries != null) { + writtenEntries.add(DocumentSummaryInformation.DEFAULT_STREAM_NAME); + } + } + } + + /** + * Writes out a given ProperySet + * @param name the (POIFS Level) name of the property to write + * @param set the PropertySet to write out + * @param outFS the POIFSFileSystem to write the property into + */ + protected void writePropertySet(String name, PropertySet set, POIFSFileSystem outFS) throws IOException { + try { + MutablePropertySet mSet = new MutablePropertySet(set); + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + mSet.write(bOut); + byte[] data = bOut.toByteArray(); + ByteArrayInputStream bIn = new ByteArrayInputStream(data); + outFS.createDocument(bIn,name); + + logger.log(POILogger.INFO, "Wrote property set " + name + " of size " + data.length); + } catch(org.apache.poi.hpsf.WritingNotSupportedException wnse) { + System.err.println("Couldn't write property set with name " + name + " as not supported by HPSF yet"); + } + } + + /** + * Copies nodes from one POIFS to the other minus the excepts + * @param source is the source POIFS to copy from + * @param target is the target POIFS to copy to + * @param excepts is a list of Strings specifying what nodes NOT to copy + */ + protected void copyNodes(POIFSFileSystem source, POIFSFileSystem target, + List excepts) throws IOException { + //System.err.println("CopyNodes called"); + + DirectoryEntry root = source.getRoot(); + DirectoryEntry newRoot = target.getRoot(); + + Iterator entries = root.getEntries(); + + while (entries.hasNext()) { + Entry entry = (Entry)entries.next(); + if (!isInList(entry.getName(), excepts)) { + copyNodeRecursively(entry,newRoot); + } + } + } + + /** + * Checks to see if the String is in the list, used when copying + * nodes between one POIFS and another + */ + private boolean isInList(String entry, List list) { + for (int k = 0; k < list.size(); k++) { + if (list.get(k).equals(entry)) { + return true; + } + } + return false; + } + + /** + * Copies an Entry into a target POIFS directory, recursively + */ + private void copyNodeRecursively(Entry entry, DirectoryEntry target) + throws IOException { + //System.err.println("copyNodeRecursively called with "+entry.getName()+ + // ","+target.getName()); + DirectoryEntry newTarget = null; + if (entry.isDirectoryEntry()) { + newTarget = target.createDirectory(entry.getName()); + Iterator entries = ((DirectoryEntry)entry).getEntries(); + + while (entries.hasNext()) { + copyNodeRecursively((Entry)entries.next(),newTarget); + } + } else { + DocumentEntry dentry = (DocumentEntry)entry; + DocumentInputStream dstream = new DocumentInputStream(dentry); + target.createDocument(dentry.getName(),dstream); + dstream.close(); + } + } +} diff --git a/src/testcases/org/apache/poi/TestPOIDocument.java b/src/testcases/org/apache/poi/TestPOIDocument.java new file mode 100644 index 000000000..3b80aba7d --- /dev/null +++ b/src/testcases/org/apache/poi/TestPOIDocument.java @@ -0,0 +1,117 @@ + +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + + + +package org.apache.poi; + + +import junit.framework.TestCase; +import java.io.*; + +import org.apache.poi.hslf.HSLFSlideShow; +import org.apache.poi.hwpf.HWPFDocument; +import org.apache.poi.poifs.filesystem.*; + +/** + * Tests that POIDocument correctly loads and saves the common + * (hspf) Document Properties + * + * @author Nick Burch (nick at torchbox dot com) + */ +public class TestPOIDocument extends TestCase { + // The POI Documents to work on + private POIDocument doc; + private POIDocument doc2; + // POIFS primed on the test (powerpoint and word) data + private POIFSFileSystem pfs; + private POIFSFileSystem pfs2; + + /** + * Set things up, using a PowerPoint document and + * a Word Document for our testing + */ + public void setUp() throws Exception { + String dirnameHSLF = System.getProperty("HSLF.testdata.path"); + String filenameHSLF = dirnameHSLF + "/basic_test_ppt_file.ppt"; + String dirnameHWPF = System.getProperty("HWPF.testdata.path"); + String filenameHWPF = dirnameHWPF + "/test2.doc"; + + FileInputStream fisHSLF = new FileInputStream(filenameHSLF); + pfs = new POIFSFileSystem(fisHSLF); + doc = new HSLFSlideShow(pfs); + + FileInputStream fisHWPF = new FileInputStream(filenameHWPF); + pfs2 = new POIFSFileSystem(fisHWPF); + doc2 = new HWPFDocument(pfs2); + } + + public void testReadProperties() throws Exception { + // We should have both sets + assertNotNull(doc.getDocumentSummaryInformation()); + assertNotNull(doc.getSummaryInformation()); + + // Check they are as expected for the test doc + assertEquals("Hogwarts", doc.getSummaryInformation().getAuthor()); + assertEquals(10598, doc.getDocumentSummaryInformation().getByteCount()); + } + + public void testReadProperties2() throws Exception { + // Check again on the word one + assertNotNull(doc2.getDocumentSummaryInformation()); + assertNotNull(doc2.getSummaryInformation()); + + assertEquals("Hogwarts", doc2.getSummaryInformation().getAuthor()); + assertEquals("", doc2.getSummaryInformation().getKeywords()); + assertEquals(0, doc2.getDocumentSummaryInformation().getByteCount()); + } + + public void testWriteProperties() throws Exception { + // Just check we can write them back out into a filesystem + POIFSFileSystem outFS = new POIFSFileSystem(); + doc.writeProperties(outFS); + + // Should now hold them + assertNotNull( + outFS.createDocumentInputStream("\005SummaryInformation") + ); + assertNotNull( + outFS.createDocumentInputStream("\005DocumentSummaryInformation") + ); + } + + public void testWriteReadProperties() throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + // Write them out + POIFSFileSystem outFS = new POIFSFileSystem(); + doc.writeProperties(outFS); + outFS.writeFilesystem(baos); + + // Create a new version + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + POIFSFileSystem inFS = new POIFSFileSystem(bais); + + // Check they're still there + doc.filesystem = inFS; + doc.readProperties(); + + // Delegate test + testReadProperties(); + } +} diff --git a/src/testcases/org/apache/poi/hslf/data/basic_test_ppt_file.ppt b/src/testcases/org/apache/poi/hslf/data/basic_test_ppt_file.ppt new file mode 100644 index 0000000000000000000000000000000000000000..af16393f44e0f315c212b60392e2bae2130987bc GIT binary patch literal 15360 zcmeHN3virO6+Zv}Z(eQE-8L;0sPu2AV1ZI1QKl-EAQh{C&5)qrpd?Lplbxp7&hFMy z(dibta-{#U~FbGs;5@7?CD#MP?#^C>?zbqby2y4G(kg5B#H#{dkTeuDHK8S zU^sv{up78e#^QN^2%rkUHLn5G0_p(c0OJAmfCj(>z!U)2eInvXfW{!tvO|zQ6fhNV z7~pWg5rAm`)*lS}4+qYc6#lZ}%g3Z2aavYll|~;9fy7v9s0(6OUEKYhMSqxh*>iHM z+;Pk^#UsVe+kVfJpSjP0>M#O^s1>uXkT~XLDyca5vHV7PNn@5m@k3Z)0n#xGbfSmAL&|>;{4np7mxUpTp+ejs+qxO?|-qA2hf6--*hIF`{&8>7@mIcdQ!*ZF%oFEX`r@k4zj^8F}rW!jZGG;fjbM!9;B(cPyY zp6QEpBNp`6m}zNI;@GQDcu`9XN69B82u$!DpQI^6T3pkE%<1qtD0*6+i{v6qMrC|t zjmDC{oCQ*yZ^CZ`JS9I#85?pygR9ANAkxS>!X+|Z`&~>4tG$HHYcD$$zp@#7C?@wh zD5||rOA1ac`Z_L1scn%XU&2%zqn0gL_6oM(z8|R@cDpxdeNkCYXthzw|TA_s6fjetfH?kZCFG8G_{oFtlEWB9D4GY0y zT`HBJ;TdnztzcZIfhgJ6-kZ$$`0wU}^F>U6!c4>TQG}cjZNoHko)jZVfj`QK)a~kbI4g60C=o z=W;oX@7n&9G&ipoS-)P^4?^Zc?XJD{T5b3Gwl*oOPsm_1nkGuy+40s77Gx06+&m~h z?kNn9_m>?n={R;g;&Ijez$Jl62a3mgLffrau|nfQ;T35f9F#(9E9M_*C%|86`Ruuf zQPI6vmZlycfq;>AIgG+~=T8^e_UKy0|DKf{+UHNV95##}sM2=Q3>wkCnRsm?dIc%` z!zpTK#%oTjm#3ZwP2k<>SDc~k@_!$hw|B|sbUf1W$3OmF)~xx8Ir&8D-2N(@ms#AT`Q2=ug*hc>`$4A)EEMICPA45EI+H&v8MK%^MuN;d}=;NR?sh4LuzgnOJFOHb7X0GOiYo3kj54)ol*vV-Oez{BRy0Zs>asraXABz^H~$FYnAGN}J*eWU zwcN`mG>+tFqs)=IyiuP96Z`ARIPJ&5Nmo=N`9~2?3d&uS)7@AL%Xbozt1wcP6WtDo2NNrp(*c?rp9QPCe8}^P?~O3oQ!cX*bJ!LmZvFxVb+k8X=sagrTV-5 zzW5j7eKa{~n?60YJe}Gms z=865U5L@i4DquhRcVoqVQ_awXQtY|6>s*6<3(}Rbhr=G=Oh?k$3;&%J@#hZat_#a% z_m;8Z@5ef(l;Y3fYu^C=?q2vmz8C%rF{b6eetoB0gBkb3Z?2?x{L3B(#`M^b|&s)*2uBs8sikb62aLYGtE9_l|Za61~@k zCBbSI?o%$hq30pQmL9JHJwx?lrRSS<+hV2kaMl_z^w2k`OwZnXb40d@;D~N1Pf<78 zSc)P6MP0CwvrxWDB2FZ*j;nx<138}vyT~uXIyuFqgjyZNb(cP_xvj8n#o&h!_th-z|8nETo92PhqXEVs^XbRMY)-`J{F!?z3X7+HFX`H z-?+UYJTs7lr7xYC<(zpTVoNhr=RRa*Kk+EGJ+zzN!iTUn!`=djsR_J=JCUYr+>W@V zST^usM%nnJ){jy)(8tQgxkY?(QO*@Q4YI+LCDeV6HQ%hxMsbFnDFcg5X)n{_b!1n+F2m|^h)7~4;#Qn~6+|TyL z{r(h=>-+ie8fEs74j{dq29ww zKKYM1GX|MYObreQ(1BD_A{^PVf_Dfn!A!kVcL;Z1V(t(aGw=?f9rpzU-XUy7Iqwh{ zGaRUQ2pbf4$$y(f;0~b$zY*}BQg;|j09*&=DZ}&>sQszDm?m(I7_%+o5$lH>zLD6U zec8{pXIqZRarl*l&w-NrU*7#w#CiYAJ6&>`XMYqKdb-8;zr6cpn}c`1V8VVD_TU`6 zinm6gK)Kld)tmnH+M?dr53a72!0wLjB0M>T^*s~Wv*|{7*nYjT#x!DAu zw!9la8QKn@&3^&Fv!Yi4l%og+qa4*E=6FwIFnSsg_3(v=-iDx9>p9lKRL_debT+fE zGUC=8^x^mSS;?++Dw9fN{Y8mHvMcVlrdGt$t*K;xR$5R$bksv^rs?h14;-zY8S=4b zp49tm?v;}O9|z1qhD*w_TKRgc2T1WGQoO4`O_iQ18SZ#+D!3cRlSEC`yDQ~p-FZjq ze%pl_424=^d_4M-n>$_hGBu{$(etQTp1pa$P&Np`h?eQ2UG&HFafiob{=K*}CW)`R zJ2)M}8q9{!g4^13va?S!<$uh0o{l%ynX0i1Jwhz&NSk{Ueoh>Bk{qWR!CqYzUwu_W zA8`a%e?85)_>CaXKYksvlyl7mW(L=oatZRJ9K>Dc%(j&{mtBfRL7>Etkg4;gs1zAo z>63`$9WNN?wYP!hV==>tA1xKbmyU}|@h85-)X-2FXYP-3VPE>k-SY638|EGnTYDY!&z#qOME&y^ z_(8kFRi?h-xN88^BLe{P>TUpS*lz)}V}k(hipK!dOIrcdM^6K|S}y>o(_RK}>|Frb zGwLPUGU_GjE53KY_LekailEg~?6au<_)J$i9`E8jUS&nUU6fdI5LFeBY^`^x&lsU+H8;)jdiN zhBwQB({Ku%#-Dj-<;ywXEnj1eHXIHKHg?DSj;0^q1I`;w!%?;w>-?h}cGS%d@cG}7 zH$~0R6K~U-qL+iuJ-c3WUVrP}6#18$H%0uktn{X6x)k&|6;!!7>j)5`Y9ITRQR#c5 zPf^7NPqwJ$2^?E(AmiW|gG!pm7*wTJp%-iVH}&4gBEU&30{ZdDIYam7{NA$&l(8rl z4|mfA2(%Jq#8bv^?N%reg;?3>CQfXabK!yNybkl@P6vTY{REg{c+9o#+Rbq z7vemlS=yP+Ci{EZc}PPQb1Hy=MvHUd5eBmed^}|oka@*L*L679Mghm^K-e%suzgsy z_fajoHcYUsCCB#Vj%r(rkK?dhV?L@yAL1N7Fm+MxLo~w2Wsw`g$7RJwwflM|(GVP$ z(UsZfQEi2ppI#cCx%jAdUym6kl$n|QEjg;i%#{z?-u`|?e z4Rf)(1%&aa_F=>w#j=6#8Olan>qmK1i#}F1&IN1ik+zF+%EnuHRQqtz_HtLQDo)GJjLZc;9G_Tz`0s{`N(@jlG90|Il-mC@dVdL{rxkHN~ChLC%Ad2 z_5I-lH+S4Ma_|J#SXo+HZf3^T@`g`vQFWkraIL_#8(?E3<^A)nk+27M{jNe`1H9_D z;#GLD2X{V1t0dr6_}lHlorSGu9Vjj(K{c6{v+f;uF>VpCazq4192$cFQK1VtMi1@` zbg~ZLTj0Tk9XOuV7*1J!{l7VIxy{$$R+U^Rch|me2ksiU5Dob^>cHhoW*)r*S2{D- q??83nXk&TEMH^c^llnL&dyYmy$iDt<2Huk56pOT4`^r}PXZSB<_=u$d literal 0 HcmV?d00001 diff --git a/src/testcases/org/apache/poi/hwpf/data/test2.doc b/src/testcases/org/apache/poi/hwpf/data/test2.doc new file mode 100755 index 0000000000000000000000000000000000000000..06921df39545bd0544f062a88c4532632cbea80e GIT binary patch literal 19968 zcmeHPU2GKB6+Sb&>mM)}C>Zlo7!s1w#IhhwBHD+<1k*Su!6`UO-;!PLtarh?vzcAT zfGX9dEk$Y`Ts1#^Knkg-ln7F}4G(>&+9a|PDQN?$M5MmaDt%~$REMA_ksNQo@9tbP zyZ+lv+O)tu@;B$+bMHC#-ZS^k%$d2~3+Go}dg<5e|0-2 zE7Furmr5myw*W{Qx{f?>`Im1<H& z&>U@(PSk#7ouzQ4qI7nqRBD|O&X?8|POHS}NI6d)PE$Hpes#IJI{r4${a&mroQt=h z=mg5ib^Yfs@{f@}73h6lz8ZFH*l7!vP%WG*>L-mNX()cwEOH$AZ$W;6@j zYx#VfcEhCYX|DBj9i8umUK@m;1pcY!tjDWFwVmob{Szi_CoHQat^2i@J#An2L-ShN zn5&Mqr`vxvm`B@RSh@fC?QikIs7TMdH&M=|rS*bh(fxMekL?h0{*iROBTLHpKL`3c zuglA&U;L=;YD?NbY-b~cdYqc;_H|x!tv?^v^{Tm^kDAY?ujPM3zc>#Uqw02aUi(?+ z=i}PGzCMKc1kSQPzhOCbbbhgN?dMuKZ!WZNOUuk0W8RSQK>Z%*OJ@oW{^Cv{lg$sh z&amevout=4ly!5(Wy>BaI_Wrzb3@sL>pNb`$;bWpfFIANp_p?rsC2-~x-2QC-K^t2 z?dF`68!ry|u9NTvlO3oNAI$VS@m$gw$|YTYFq3nWPSHIK-DRo=iA<5bfW5er%%oDT zkJg=(hXb_=nWJvFYGA(L6xgyK80$n8^cQwK^u!;|6kO+N*FRD|Bs@Qp8|YkCzmINc z4cu*>z;j+d-~xw$Ja7~^2AlyVfVTlY4^Ca3x;*v4)WxZHrfz7r{4WQ$W=+~|UMFqp z_`n?v`7|&18^hAwd$&pN-iUk$h$Ahn;nMr=uqyKRHjP%~?`axsC|h63=lsn$uMW@+ z>;XoAQ9z&nvq--Rya${IE&!YhpM{-t-DJX@zNyoEdRVUGyy=_T6*uSLZOgJcWXD+A z+B|-&(;0tj*Ou|7##?|}$44c4 zfUc#q#Bk$*|E34JM7lmK+0rE~Gn21D_K7FHWF;E9Xt7EzyZ2}J-`Ss3LFE@0B3<$f%ky(zy;tc@N+=DY9b#A zz%{GdDGjgBaxK>8f+)Yv3a2@U2iwMA%C;LKv1rVWM1Oo#9Ecg4C{hg})raMh#WF0Q zcpvyM__B-?&}1zO*4~ljd!G z7cLqZv_zfz1Dz#N+fq8=^^5Ws0n#-cM;?G?T>9ZFzBG@@Q%xt@W%2?v(-=KGdFU6n zHv>#t>H@eGDK2Q95}x453i3*+QiXI&L?$^6tyZjh;s-|N3O96=(i_kq@v)@Fw{C^MMPQbeB4Y52ezoAiD{x zC#`NbPH>EigO$V;l@D%i!&ni%Bq2{C)sNdISDU^$@&eKW`2f}(Z=*$M&z6K+H)-Pm z;{oFV&h~jL+yw3b3xCfl*JB?^)M0x)-oS9&FRC3bl!eQ~Y1ZB6 z`PukjXVNP!tiJK3IqV!z z>GyxXhr0;qR}546cWc1;_(!=R;{oFV;{oFV;{oFV;{oFV;{oFV;{oFVlbst@{Okq9{~e$Hit#_cHZTrmOqvE5m*)Y-`#%I2?>__Z9l^7}THqwWub!s? z#_wkV#_#6WGc>A)35+r}|}t+aMbWD+^ctJnb)RvA)tT(mv;|x!#67cypyKoc#!H@EmpZwVU_oIrpwP-NEle msBL?X9N>O7h}1QO=}ukVxl^uw^Edy=wN~HCFtr!+z`p^Qy8uc6 literal 0 HcmV?d00001