From 47dd4545797ec8e0e789445931d3897a98cf384c Mon Sep 17 00:00:00 2001 From: Evgeniy Berlog Date: Fri, 13 Jul 2012 15:45:01 +0000 Subject: [PATCH] moved HSSFObjectData into drawing layer git-svn-id: https://svn.apache.org/repos/asf/poi/branches/gsoc2012@1361278 13f79535-47bb-0310-9956-ffa450edef68 --- .../poi/hssf/usermodel/HSSFObjectData.java | 45 +++++++----- .../poi/hssf/usermodel/HSSFPatriarch.java | 2 +- .../poi/hssf/usermodel/HSSFShapeFactory.java | 68 ++++++++++-------- .../poi/hssf/usermodel/HSSFWorkbook.java | 35 +++++---- .../poi/hssf/model/TestDrawingShapes.java | 2 +- .../hssf/usermodel/TestEmbeddedObjects.java | 24 +++++++ test-data/spreadsheet/drawings.xls | Bin 836096 -> 851968 bytes 7 files changed, 110 insertions(+), 66 deletions(-) create mode 100644 src/testcases/org/apache/poi/hssf/usermodel/TestEmbeddedObjects.java diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFObjectData.java b/src/java/org/apache/poi/hssf/usermodel/HSSFObjectData.java index 5b2a463f4c..efb19331b0 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFObjectData.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFObjectData.java @@ -21,6 +21,7 @@ package org.apache.poi.hssf.usermodel; import java.io.IOException; import java.util.Iterator; +import org.apache.poi.ddf.EscherContainerRecord; import org.apache.poi.hssf.record.EmbeddedObjectRefSubRecord; import org.apache.poi.hssf.record.ObjRecord; import org.apache.poi.hssf.record.SubRecord; @@ -31,29 +32,19 @@ import org.apache.poi.util.HexDump; /** * Represents binary object (i.e. OLE) data stored in the file. Eg. A GIF, JPEG etc... * + * Right now, 13, july, 2012 can not be created from scratch + * * @author Daniel Noll */ -public final class HSSFObjectData { - /** - * Underlying object record ultimately containing a reference to the object. - */ - private final ObjRecord _record; - +public final class HSSFObjectData extends HSSFShape{ /** * Reference to the filesystem root, required for retrieving the object data. */ private final DirectoryEntry _root; - /** - * Constructs object data by wrapping a lower level object record. - * - * @param record the low-level object record. - * @param root the root of the filesystem, required for retrieving the object data. - */ - public HSSFObjectData(ObjRecord record, DirectoryEntry root) - { - _record = record; - _root = root; + public HSSFObjectData(EscherContainerRecord spContainer, ObjRecord objRecord, DirectoryEntry _root) { + super(spContainer, objRecord); + this._root = _root; } /** @@ -110,7 +101,7 @@ public final class HSSFObjectData { * Exception if there wasn't one */ protected EmbeddedObjectRefSubRecord findObjectRecord() { - Iterator subRecordIter = _record.getSubRecords().iterator(); + Iterator subRecordIter = getObjRecord().getSubRecords().iterator(); while (subRecordIter.hasNext()) { Object subRecord = subRecordIter.next(); @@ -121,4 +112,24 @@ public final class HSSFObjectData { throw new IllegalStateException("Object data does not contain a reference to an embedded object OLE2 directory"); } + + @Override + protected EscherContainerRecord createSpContainer() { + throw new IllegalStateException("HSSFObjectData cannot be created from scratch"); + } + + @Override + protected ObjRecord createObjRecord() { + throw new IllegalStateException("HSSFObjectData cannot be created from scratch"); + } + + @Override + protected void afterRemove(HSSFPatriarch patriarch) { + throw new IllegalStateException("HSSFObjectData cannot be created from scratch"); + } + + @Override + void afterInsert(HSSFPatriarch patriarch) { + throw new IllegalStateException("HSSFObjectData cannot be created from scratch"); + } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java b/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java index fbe3f56b68..221dfc475a 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java @@ -387,7 +387,7 @@ public final class HSSFPatriarch implements HSSFShapeContainer, Drawing { if (i == 0){ continue; } else { - HSSFShapeFactory.createShapeTree(spContainer, _boundAggregate, this); + HSSFShapeFactory.createShapeTree(spContainer, _boundAggregate, this, _sheet.getWorkbook().getRootDirectory()); } } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFShapeFactory.java b/src/java/org/apache/poi/hssf/usermodel/HSSFShapeFactory.java index 4bf927b2a0..5d2c9bb619 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFShapeFactory.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFShapeFactory.java @@ -18,18 +18,14 @@ package org.apache.poi.hssf.usermodel; import org.apache.poi.ddf.*; -import org.apache.poi.hssf.model.TextboxShape; -import org.apache.poi.hssf.record.CommonObjectDataSubRecord; -import org.apache.poi.hssf.record.EscherAggregate; -import org.apache.poi.hssf.record.NoteRecord; -import org.apache.poi.hssf.record.ObjRecord; -import org.apache.poi.hssf.record.Record; -import org.apache.poi.hssf.record.TextObjectRecord; +import org.apache.poi.hssf.record.*; import org.apache.poi.hssf.usermodel.drawing.HSSFShapeType; +import org.apache.poi.poifs.filesystem.DirectoryNode; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; @@ -43,7 +39,7 @@ public class HSSFShapeFactory { private static final ReflectionConstructorShapeCreator shapeCreator = new ReflectionConstructorShapeCreator(shapeTypeToClass); static { - for (HSSFShapeType type: HSSFShapeType.values()){ + for (HSSFShapeType type : HSSFShapeType.values()) { shapeTypeToClass.put(type.getType(), type); } } @@ -56,60 +52,65 @@ public class HSSFShapeFactory { this.shapeTypeToClass = shapeTypeToClass; } - public HSSFShape createNewShape(Short type, EscherContainerRecord spContainer, ObjRecord objRecord){ - if (!shapeTypeToClass.containsKey(type)){ + public HSSFShape createNewShape(Short type, EscherContainerRecord spContainer, ObjRecord objRecord) { + if (!shapeTypeToClass.containsKey(type)) { return new HSSFUnknownShape(spContainer, objRecord); } Class clazz = shapeTypeToClass.get(type).getShape(); - if (null == clazz){ + if (null == clazz) { //System.out.println("No class attached to shape type: "+type); return new HSSFUnknownShape(spContainer, objRecord); } - try{ + try { Constructor constructor = clazz.getConstructor(new Class[]{EscherContainerRecord.class, ObjRecord.class}); return (HSSFShape) constructor.newInstance(spContainer, objRecord); } catch (NoSuchMethodException e) { - throw new IllegalStateException(clazz.getName() +" doesn't have required for shapes constructor"); + throw new IllegalStateException(clazz.getName() + " doesn't have required for shapes constructor"); } catch (Exception e) { throw new IllegalStateException("Couldn't create new instance of " + clazz.getName()); } } } - public static void createShapeTree(EscherContainerRecord container, EscherAggregate agg, HSSFShapeContainer out){ - if(container.getRecordId() == EscherContainerRecord.SPGR_CONTAINER){ + public static void createShapeTree(EscherContainerRecord container, EscherAggregate agg, HSSFShapeContainer out, DirectoryNode root) { + if (container.getRecordId() == EscherContainerRecord.SPGR_CONTAINER) { HSSFShapeGroup group = new HSSFShapeGroup(container, null /* shape containers don't have a associated Obj record*/); List children = container.getChildContainers(); // skip the first child record, it is group descriptor - for(int i = 0; i < children.size(); i++) { + for (int i = 0; i < children.size(); i++) { EscherContainerRecord spContainer = children.get(i); - if(i == 0){ - EscherSpgrRecord spgr = (EscherSpgrRecord)spContainer.getChildById(EscherSpgrRecord.RECORD_ID); + if (i == 0) { + EscherSpgrRecord spgr = (EscherSpgrRecord) spContainer.getChildById(EscherSpgrRecord.RECORD_ID); } else { - createShapeTree(spContainer, agg, group); + createShapeTree(spContainer, agg, group, root); } } out.addShape(group); - } else if (container.getRecordId() == EscherContainerRecord.SP_CONTAINER){ + } else if (container.getRecordId() == EscherContainerRecord.SP_CONTAINER) { Map shapeToObj = agg.getShapeToObjMapping(); EscherSpRecord spRecord = null; ObjRecord objRecord = null; TextObjectRecord txtRecord = null; - for(EscherRecord record : container.getChildRecords()) { - switch(record.getRecordId()) { + for (EscherRecord record : container.getChildRecords()) { + switch (record.getRecordId()) { case EscherSpRecord.RECORD_ID: - spRecord = (EscherSpRecord)record; + spRecord = (EscherSpRecord) record; break; case EscherClientDataRecord.RECORD_ID: - objRecord = (ObjRecord)shapeToObj.get(record); + objRecord = (ObjRecord) shapeToObj.get(record); break; case EscherTextboxRecord.RECORD_ID: - txtRecord = (TextObjectRecord)shapeToObj.get(record); + txtRecord = (TextObjectRecord) shapeToObj.get(record); break; } } + if (isEmbeddedObject(objRecord)){ + HSSFObjectData objectData = new HSSFObjectData(container, objRecord, root); + out.addShape(objectData); + return; + } CommonObjectDataSubRecord cmo = (CommonObjectDataSubRecord) objRecord.getSubRecords().get(0); HSSFShape shape = null; switch (cmo.getObjectType()) { @@ -128,8 +129,8 @@ public class HSSFShapeFactory { case CommonObjectDataSubRecord.OBJECT_TYPE_MICROSOFT_OFFICE_DRAWING: EscherOptRecord optRecord = container.getChildById(EscherOptRecord.RECORD_ID); EscherProperty property = optRecord.lookup(EscherProperties.GEOMETRY__VERTICES); - if (null != property){ - shape = new HSSFPolygon(container, objRecord); + if (null != property) { + shape = new HSSFPolygon(container, objRecord); } else { shape = new HSSFSimpleShape(container, objRecord); } @@ -143,9 +144,20 @@ public class HSSFShapeFactory { default: shape = new HSSFSimpleShape(container, objRecord); } - if (null != shape){ + if (null != shape) { out.addShape(shape); } } } + + private static boolean isEmbeddedObject(ObjRecord obj) { + Iterator subRecordIter = obj.getSubRecords().iterator(); + while (subRecordIter.hasNext()) { + SubRecord sub = subRecordIter.next(); + if (sub instanceof EmbeddedObjectRefSubRecord) { + return true; + } + } + return false; + } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java index e24ccc2d91..332289e109 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java @@ -1706,7 +1706,7 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss List objects = new ArrayList(); for (int i = 0; i < getNumberOfSheets(); i++) { - getAllEmbeddedObjects(getSheetAt(i).getSheet().getRecords(), objects); + getAllEmbeddedObjects(getSheetAt(i), objects); } return objects; } @@ -1714,27 +1714,20 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss /** * Gets all embedded OLE2 objects from the Workbook. * - * @param records the list of records to search. + * @param sheet embedded object attached to * @param objects the list of embedded objects to populate. */ - private void getAllEmbeddedObjects(List records, List objects) + private void getAllEmbeddedObjects(HSSFSheet sheet, List objects) { - for (RecordBase obj : records) { - if (obj instanceof ObjRecord) - { - // TODO: More convenient way of determining if there is stored binary. - // TODO: Link to the data stored in the other stream. - Iterator subRecordIter = ((ObjRecord) obj).getSubRecords().iterator(); - while (subRecordIter.hasNext()) - { - SubRecord sub = subRecordIter.next(); - if (sub instanceof EmbeddedObjectRefSubRecord) - { - objects.add(new HSSFObjectData((ObjRecord) obj, directory)); - } - } - } - } + HSSFPatriarch patriarch = sheet.getDrawingPatriarch(); + if (null == patriarch){ + return; + } + for (HSSFShape shape: patriarch.getChildren()){ + if (shape instanceof HSSFObjectData){ + objects.add((HSSFObjectData) shape); + } + } } public HSSFCreationHelper getCreationHelper() { @@ -1808,4 +1801,8 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss public boolean changeExternalReference(String oldUrl, String newUrl) { return workbook.changeExternalReference(oldUrl, newUrl); } + + public DirectoryNode getRootDirectory(){ + return directory; + } } diff --git a/src/testcases/org/apache/poi/hssf/model/TestDrawingShapes.java b/src/testcases/org/apache/poi/hssf/model/TestDrawingShapes.java index 7d5bfaee6d..dd8882764f 100644 --- a/src/testcases/org/apache/poi/hssf/model/TestDrawingShapes.java +++ b/src/testcases/org/apache/poi/hssf/model/TestDrawingShapes.java @@ -198,7 +198,7 @@ public class TestDrawingShapes extends TestCase { assertEquals(1, drawing.getChildren().size()); HSSFPicture picture = (HSSFPicture) drawing.getChildren().get(0); - assertEquals(picture.getPictureIndex(), 1); + assertEquals(picture.getPictureIndex(), 2); assertEquals(picture.getLineStyleColor(), HSSFShape.LINESTYLE__COLOR_DEFAULT); assertEquals(picture.getFillColor(), 0x5DC943); assertEquals(picture.getLineWidth(), HSSFShape.LINEWIDTH_DEFAULT); diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestEmbeddedObjects.java b/src/testcases/org/apache/poi/hssf/usermodel/TestEmbeddedObjects.java new file mode 100644 index 0000000000..840643b951 --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestEmbeddedObjects.java @@ -0,0 +1,24 @@ +package org.apache.poi.hssf.usermodel; + +import junit.framework.TestCase; +import org.apache.poi.hssf.HSSFTestDataSamples; + +import java.io.IOException; +import java.util.List; + +/** + * @author Evgeniy Berlog + * @date 13.07.12 + */ +public class TestEmbeddedObjects extends TestCase{ + + public void testReadExistingObject() throws IOException { + HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("drawings.xls"); + List list = wb.getAllEmbeddedObjects(); + assertEquals(list.size(), 1); + HSSFObjectData obj = list.get(0); + assertNotNull(obj.getObjectData()); + assertNotNull(obj.getDirectory()); + assertNotNull(obj.getOLE2ClassName()); + } +} diff --git a/test-data/spreadsheet/drawings.xls b/test-data/spreadsheet/drawings.xls index eeac150c3f4bf3c2329e3cecf5c56b706220d337..45b9a958870d9362f8ade14e7b93e6b5d7159207 100644 GIT binary patch delta 17270 zcmeIacT^P5)-F6V40#A5NiqmX4uT*#s$@{IB01-rVaP~M;-CZ(5F|>HAt#BF1tkaq zN)RNY2!il+gYrAa_q^Xacdh&9ZPwF0_4MAmcI~REuCA&fC`=$INH9@co*DvyKmi<| zoSXoc(<1;l9|J&wH((Hm34l)k%rGF31ppRs2xJL>6($7o1pu1A9uWjhhQVP}kQ6MK zAt7k&4+62OTwx?o98#eWP}u?9y|^MG9|@=KrTXNpeROP#6=v2oJlBKHUx&AZ${8VQCrQ38f3p# zuVXtOag*+5m;c|U{(N3JZfJ<(BU*lWj;+(Fxi{UFogzKxxl-^S&1*fb&MY&NX#s& zb-N(`mnBv~b=wo|!&&$JSm}96mIh0CGL_y%)M|CnxeKQTaSGxN<}S2*kVCX{uYJj~ zQou2vOVht7Oi{jLI8JU6K<)Y7`EK0hzSM37y6rOkKwJ5X^dK#k%<2BAi-7WQtuJm8rj z=gnVrR)CC69#VVFhv?c=Xp~zxTy<2ydG@?PMWKOFi>{S6r(cZ{A?_s*> z_~DDnm>8=1hewx91!>hu@weAppC|F%eq&-il5*(<(qj7IqC`Xsod&n~yEa=KY(MNG z$)Dl}m1sd#CvV=R08^A&taR2zu|Cg5IZOP)p${*f$o46QlI>=UUg$k!GQ-GXoib4W zc2aZQg-JPTSYd8gd^Xxm`s>$Vo~PD@`%Q!5p9;d}Rn7NJ+B%7}jDWzLoM1rCTR5uR z6M=d4BknIY7|_JWfbSF)1PAJE&wCa&FDF+!Qzs`o9&ZQx-ywv9A%qqH%^r$uOc}C0 z4PIzd=tzJ|vZKXA(WY3Lm-l`^SZ{wC?htykcg0C6MXp}Dm**KpDDzQ()NsMC`GU<3 z3M|`9?L-!Koa*PrcR#T0UvH8W4`p~?^ER{Ug|T#qoC^|-e68koB%lACIVYM0Gh%Wx zFYm4KOLiAJhm0|vZ%@oFQLP|7eBKkh5m!ZDJ$e^pn$I4e8sc0c#Gn?MFnD6uQ17Vh z@3Lr>@aUypj}yh9Ik8Ce>M#Rm+Fj=EUe>4%hL^7wgd=)U#+B5s&6W8+Tr=tU67)_i zGEKI=#P+>mh|gWJh)AA@Ks-d;_vBxnpX|}7{EX^?s3DdD+8nV*o z9N7xy^Exq^sPDonb9iOb76$z!-6y>h5SqE%cO#CyHs;)}DQL6$2K79A!9Z4`{#;k<8ROA`{BR7T7j~^9 zj3s7NpYE1r?&DsoCz)!N$+*Q|B-qvK{H9Vi9OK@ihBo}7yM4cBQ~z4XU4sBaZ-{iWko+%iY?-!Gibn%n!EnHT@w+>Kl|Tm$wOXo6t2E#o}^Bwbv%#Q`NWE z$iH7VOz6Z5hr{8!805`T#fHE5TmB8zz>yOAzx%f3~UCGRxvb7^8!8Wp?kFYes&aA<3zPV5|C+IqKi zxA@h<96`?~)Y4Wa=lADDF~Zr2Bi>eor5IBydD8`nj8@d)TNXjQD9wEY<|N;M5VvWM znaBI-m@Nq&QR!^lOFDu#Z8H)X_ZUa+U0=nIekI)~>%0+ne+RiZo%|>+kJmbf>Z2cS zM9$-FKcD@XzSrGf7U$bWj#F}fJ&mW66rVb2+i}GacBWatHS*uxrZ*CoZjn0P>C9D+ z*}j@^vUo@o82D3>pP0ioe`I%vFfhP%>u8qaRK%NcS=KLedx3Nx2W=un)0j z1_O#l83{QQrmGsrIbv^$a>wFf=tmkeoiIC+aUUvBo&jb|8TcKRYjKW@5ca2dqz)H=j^#h9a=*TQNfl(LI06<79~$w+Pua7UPhV~|`7 z|0dQfrx5Jn#qM!buNctALeJrj%3;k9=UEwPPEioZA)t~$8g$(dl6yfrYyVM3buhZb z@Ja&@2_CEpw&)io;^>c^KpG#dx98lkL)a3mt(Rx+#dRcJ#!V(K$`G5o7o?ys@1`8o zRF`ekJ=}u#y1BP2DTXw9gLJzwSY+t(_<u<03o6V+*CFW2%xM;&?7aYUC zSUtdqS4aGyMJi=DKmCZ~INEyK<`Kis0fjGmvY$PFI>~=-vF(bL6|NE4%D*Jscx~cI zw36v(J$y;rAH4RBU%g44F7$jqh+9^j4H)%mI`N;CD10!zP&cT9_1Oy}oP3qbc1THWEug*~w z*yBniK1?9JFymYv`O}`chCj@xfMY@+o5Yb*<<>Bf z^Sk^e2gAAn5$^gUOjO&XM}IPL!MYC0TBn!Xan-vh)aOkhb`9C8W2{&Ud;S7%&qIHz zlOdInLtLZ*Jr|)n+`smSjB>@-!Q0?Pq8x>i?5Ya0snvXm6~%_V>Q zAVDfylS%S&CdCasMPO_UzYx_hG48FK&K zTk0MA%>n8hqyDZETOBe6E;QNLXeIIn*t)H6>6DH5aUigzzC_?QGnZj}EFKHRJ= zEZp6APcK)&hgxiks?!H>6oRow51(01s8+;(?K<+F?fvT@lXXvBhbeN~n6fnHXP|AGdBT{a!}!+cuM;&Nf5p z(nUsrrzXauFYuH~-eXt`%OJkJz5T03Gk5WZA?MCPL|X98tq+2ny5c-Li3bTGr4rW# zW_$2I6?=)l54j>=J}kGGr1EwaiM8=;6O;Gl7w(U+lz4co)u(x%TS%PJ6Ty;*n`M4K z@X@m}uQGGJ3f{BbdTQ`ZVc^1)UR{ z%MGcwLy0k$dm47)OZ+UZa$?kcOHK@+Pab5vxtPOM=JU0ie?S`(F6+`>f3rAfsoAHLp$80z7Y zB3p+YBkXEkwO5;53jw;Jq7rxXkJsV@=*1m#TLu+&hQbs@5{)Hu6hwM%tnUPw%6#?O z%d!g~gy%c-_wDZeZs->~xGuK;H-$cJ?tcmmyqyrsZ-yU*PWUm4MQTK|7%4H1v;KnI zF#M3Sr(}(0IXL_>5hfe|+Uz6WeWX6c_n2@qCj3%IX~u9t`6pcdnUXaxv-QLY0+EVg z4Fk$QqGC{YKZ#iCypy#zA@6nSa!zykUZQ!k$XvzZ`yI2#Dtq%E)U7>LtArO{ zVoYah(qiE+@>OT6jT@kzHjG~H3fg2pDgjH~%aUv?lA|EeqrKi229)~NUYgfW8r;~g z{WES}z}NYIzNaAtpW!C|S3wXse_;b^{wxT=r$M+9ekWwnK$c-#=oduWu#X80!t;Z} zRlRiC1TYBeD}GsH6y)(yTx%W}c)bOZcgnW{8`cb^fvTTDKEwg4etyhBc%4&C%{Hn# zO<()8>gnT5q&}_b6~t=wCcatA>mpI{Jl=pkksB3H-}wNYX)i2m3U^)qk?ojZfI9Ii zFnK^X)QbeFzWX!?K5|LEiN#Z?VAWr{w|wQ-1zeT!C!M<+z$DRs2trwO2on#`^p^nq zFJ0bg@ne(zdGYG;dO|2DtvQNo^JD^wyX`S_7ajM$E9o=;{*OFbIR%5-$?dP>ff=R; z{NGk-MMQV7EcFTT0&cw8Ee#$=eedLc!S;T<=1KyVB!4@yQe29Yu%lsuFJfB{rq;!zDM$pvUziTITFVs9Ph{UYiqSTCox=|}HzVn^8f=b1mR zM5g~-_R5=F!NCxux+@MPd7PPv4D-H*^l$HZ^w#TDf~VE2`Iu}sPv|j^qfzivzqp>R zp}khYVr6#63ywrC*96@3dL=u)?H!X}wnvcTGhk02z? z*O|RKg|@$XTvTg2D0fwjbc3oa(UAq2lF6<4*20O`dB>}P50x4FKh1NlY z2j1aUoU-?qCui^W3w0{Hklc;gUN7!m#x7s{ME3EE8qTl1V5d-*glt|nX6EjK&So~U z$KhkiE^z{DKKCvhjIrj|6(UborOsZlamYLE_<(jajR(w`o;{x2tW8`k%r)IzZ5*xK zz^-XQXT))ek2K(EHM0C@nnX@6m?Mu!L0ol|uAYdmH{9E8ZEW0R_~p~gRxC>XsP?;s z3fT*;pZ)wddL`88be}UT^RZP^jXFl&hH;G6X~gjVIu>lAy%X}NI71m(YOFFI^o{@5 z5xY!vheWZn3cg)&BwKq)P_o%A-Rx;8HwPuhnNoduy-RZ<zbMqSy1s=JRV+; zXXz^9&Br5IP4n+g3R$#2PrEd~yVOYPIuqeH5Q-^`liooW6HsD9Uak0CV`|@cOsmEQ zqbz)J-k$hA&Oq;*hS=JAr07N&b)AH9rvN&j;0nA7K4sd(2%SPNAUB^puv|Vqpe$01L$I7ja3C9zCUm)e#y7us3llL;3o=izfJ=^IA>X=`|QCUtEm~x4=#dV)1JP~{k0=^ z#F~=GROrEB(_xo<*|j@~fnyfon-A)BWkz0I7}i@U!$ofdbcCS;i)Wzy)M80T7QZ+k zSJa?ZJ#0armWOe*XB}8NM7`8xLIaT#oAJIChlbx9lfMr1+S{R%#4__+plAJ4QOcWl zF+jT_#Dzczezz-kAA5__2GyveS~kT;{84w6mTQw9cfLhwhg?A+^={$Y+&gHsPeCl* z@%GpeeqT$8plga@}^ea zRWs8I^DlQNXDBZ_JV5r~ph9kX%Ux?um~Fv-HR=4bJa3F4h*-2;+3XEbR>3F>_k)d| zWIXR=Wo=>%1dmGg{8PTfd&^60$0bdP zhwN$)QIKE_mS-$zwQ%Xm4<(&LM|Faz{w zWt6;)LWo#?>t+kj$5qR1!)_$a$wa-sgYC;)G`lzJj6^J=D)oz(Z%VJZAZ0A}jN)+Q zCZ@Jmrrl8OcV2V}X>Qr01kR88nV%)u_uT|-ljOaH(tqxmrR>yih<1N>?ap+Y+Zk-8 z^}5)&nQ*j{j*Wba3@o!yUqx2FY!Pc2jfBzsxD|jLqle204}N6jE)y3-$andOMPrCX z$y|tJ;g8_p55=@yILHqZ;hHln@BH-6lUI+IbkaSvZ3GKV z;@w@|Yd6WDdFjf4V&_0(0Ofq>(PPUJj_hEnVqSZexX=e%GRNkeg<|g7-rh-3C%+%2 z9VuPYDL^A71{RsFoHbTwp!ajPaMiSMcL%#c-oMgUc}#5_O|yylDk-Zzn(&!eN)EQR z;)D^m_OuQXx4}s+DG;xH`qbC|rWMkD2@_8;sX*d5_zr{j2%P|M=?$L6PXyKJ#Wd$VZ26WPaye%jgPQnLbNVR<0ay@F9iAZBM=M- zYyxNUXuqJGo{f&q8mK3C##;~S{+P}+W7@tm&YLg|(@3Q0i!maf{&sqZ4?QcJXe zh{u~ogW%})8|pSnh4RaaZpZLyr^gNR;$l&IR!fs=1GR%?VHlN@kZkJPOSJ4o zU--U}bVj8|XLNicZ%vB!zOS&dll%ZfkEhu+J+zL5g%bJ?kx3&6#L0A#wxWD`(w1gmhe+ze?#2>F2M$dRGvX6TWd2?VESzZqxx+*R;Z`1qBpux`2AO^6!;>A*TZM9 z$y*!d9JUCr^BTFWel>uO5R{Kut$j9@x@RGuMz&gM)-fV5whPpq|1{An&fSuH@pC>lA1f2X}CH$yYZSi zxmtjuT85l|T^#jKzOVe6k2GQv^F*#UAl-6i3C97OqrsIy?{*`8@1%~sq(aWjag&Xh z9ro+Kg@E0KUdDr;Z0TR9ONw7{5G&px4gPd@%+@G--1g#2jRkSdkZX5kg4O1R>l>ST z>z*0h^RAf+`hKmJE%9OQH0hVQre~C5fskGU5-N@`F$b1iq>0Us3BKdry&x-_`VJED zi~R!AP%{CNkxT6R4+M>BR-hqf*3uz6gehTjEoIo8Gw(10uG+krdaB|*s_*rN+#jcV zZ{(d>g~Nxu0pGYe^{vCbmFGxXgJMS=8jZ*JYIT@PuejaB?}xQr>pyTv+b+X$LE;^x zO0@aSKw(UHFfsSxU#cG~BfD)w-^@8v6cnd%6}c-?u(RwYY`lrt%^WUg9HKZ;X0Ey} zt*DbElF4l?#BvP7+U!%;I6i&yumv3EFXaRARstqNi2pU-CeF_PGu#;|ZH~WZI0+** z;xei!Bcyfs^EiDc${~t=x)!43tjZxBRK1QLJQB(&Cd~w$w5D#76nwRjOgdVjf2yEp z;6$DNia2Gv%y?9B@T&6^s>=b-v!%Idzo#tvjzTwFbR zI&WBskFanh z#1@hfe-NKH@ciG0;^8z)jCgQBxY z=YrDR85t?E4qB#ChN(gG+nng&o#EY=!!u7J=WTjc?2|{wjULIVZjD|}+~pS2!fRR@3rB7#C?EQSwQ?>SBg(nuKqp!{*Q{%h4UQ#Z8H8;PyFvG+<){G zfmP|>p8eMQdqVfLY~p{i7=g2>{XNt4kKQYa|ER}*s>k#9%)&o*4|2EHXs&nPEz8@EXcc7&kEU@n`?o-(#15jPCzs6#1Xu3{J<> z|Gm|4rvJv()A7+?M!6vW{)qd>QwW5F1p@iE2i|GKr2oB-{#S)4;AP=l;dhU%p^OQ9 zV*z8p-!brJWWj*~_7TNAYDKuG@WJ624#4KYL%1RW+?@vsf>&^N9u$`1fvJX)Yen#5 zfxI3R^|J-xD+6v91awXh;6ZV^#SwT|K){`S;1)q}haxbEe7cbjczuBYUt?fT*clH_ zAsmDI8vCHMP(Ts`xC!RRhXXv`fur^G47o{z%4|l^v7YMQ@HVrsS5@J^db-i)G{vI{ zF17Ih)HjhzmlQ@g{i7xX4T`1_@f_zF@#(Hd3~<|_FhKJ`*--pV2xS-^H02nx}2m_<>UxbFLQrWfGG;V7N}~Kf2L~TU}|A*ZegDCQ&Deo{!AxCsC`jb>woj`1X8(>Cf2Ld83LA7NHaOa}GCAEMa?ayHhd{6}7 zx=R32^}>Vj0rHSzZ|H!Yox9dMvgT)O&%;Ud^`v95!gw5tHS8R8V_5ohC_LGVi#v5X zIdIqrkZc$3tW=gVMcJa?zj2aBS28~o}2=;oVdC;6GhoxOw^P25{9Ek^nR-@s=FiP zvt>3%x^CY^-k!{p2l~s6QUwS)kjzgS20CW!eM6R1P*DsAD(qi0*ua*AN={`~2So8V zi@e*JxIN})-M7AJ|3> z*6WenqIso^bWH8IlMkiD+M3D??dju(7uB$pHs968@6OtN`|6$)=uIT~(~se}eoYa6 z94KjM_)W9%TRL`y`+VaGwPVd}Y)@0U$Jn#7Ew0$Q!WR-`F;mrY)=E7C|tn)Qj3}2@A$j?*Db}roh*M7d%c_!t~fqnW| zQUw(fUNw16XV;MZh_C&v4u+i_0T(dDA=7)pX~M7F`qmxyQCAG#IO;+t-#B@jJy%d2 zEB(^yE+vk>2uI6&kK{zz6XSKEwwrnlaMiIVJnqyO&s&$BVx+O zP{?+JE24O*5y!*2H1(od!a2Kt%(;GY)ME9Ijo!b{mM{~wlSW%h+C03Bv{F|pfu}9;g4?MM4Mk$)sd8*q!jz&b+W`0x0A5o$GFvmy4L0c2= zJK-(KlC;^--Ork(tSJ(3J73TMAL*fWta%%WZM?au^Fs1~eJ$XAwb-Or`}^@{{w4)N z_nfE$wx6ZLAR~;; zwaFd3*N>w#F}^?Gojxp3XSRG3|7%o&wZbHI!rIb6Q2RwjqIk3ZkyUqCE5AE|XO57B z3i7+D7?xjQF^X!B!(s2ZSSfPwWp`&M+mL|CU^V~E`&9LhQMud89m#Ha^n3GxW0%_J zjd>I84xQZtTL`w5g34H+VgKK(IRksa}6xEII+|z6*wQ3 zv8}GcX4r7~#XzE$ZSB5NR40tOzuXi~jzC_NYowafmKy24Fp`vNBkg8}L8DU@(h0;lp%WNU-fCLR)Qhl&!VUJ2zu^3e3-YonQk^}DGxiM+RGk9Qs#%IUKn8D@mX zXQ`pF%+$F9U;Vh$De-yWK;fFDZ>OB6no#VucR{avXVcu|Gcn~uGcc~bm|}mWe9-Dc zNU*|acr*%YDc;|_>q&HnCvV4!6}2$CvI9S{bK<&lw5`uTy+N57KB&{W>a|hclCG_L|l(sD> zx_t+&egBJz#L0BbEU&e%lcFYjh?x-5*lx$O^Q32wrS@dzhBu>WQ#AHawk+pDHk0f+WW=i<>~8XvK5Ujn8S)1teQPC~mR^4iQPPo@mNB zsf%$`T^z}J8e7xIT%D;d&v~tSp=(>kG?9!tdfkbu`;iClT;@AuM|vNH$xJe`du-E2 z{p9dvx(SC6*}QZhj?kr#a)pJY2Obp8&$6t!f}U|)>?OP6{9>PPZ(;M6jT?KG_sO#}%Xm5i`XCqVlusi1wqncqWXyyXx)kLRl^sp6^Lo?X4Z^S!t&?@9*`f53S3& z8R2_o&IV}uC$|t~iu;|s6L^!+h!ky64&|f>I}vPo&Cp_UaA=lY1J~sZm|w~ga#*Dm zJ*izm4>tU0dR#+eCIoD6xnPv~AhB}D@j(D4Ht9+K_y$+XD~o}RPwwty&kHHtzZ`jT z4l+kH9ANjhxQ@I|4DfUPO689fcUcKYw<1^{oC$w3h}X$@cyx7ow|+M6$0oAq2E)l@ z9**$IlLF+y^XdACd#nkWD`<1ug#f1kL5Zz-nrVbv$YwpX#fv6G%tddz<#4Jh7qI%R zS@5vd3U7M)(Y{%eYj46?;es>Oo z1Qx~HoTR5zH)!PO>^}K0@h&1Ce998_C~8#NzgshVTbp=Re9MSBk3!~womg*CgPn4vZDMXGbRH=O`KG9u~ z+tqwu*UhxLG_Y(?@!jvz~lgl?5b*giI)OZ7$AppGR52;NdH>*mC1fK8AgCI&TeS{8I1bmMM;NJ#oM1Tem5~ zT2prKr$`4pWOGMo&~@@tOSg@be2<@?&&&N^kY#<9&({ig5{a&qec0*GqzvO_9E!!Q zJwDzNqe+)l*k$QB7S@XC_N*$pPDKX$3Q3W{x?0t&Mh#8Tz#>g)R;#I+RFA@`veXM9 zgNs8L7^=QFWZ_jsd;duy;(=sY+Mgs}8IXKi>kF&wdH}`w3=B^f0Hh>#n>SkM58 zh`JEG7Ngy$peokmqfW%G%^e(if2O0~-F$dhT0DyYf3pK|H0zQGxqE(up z)p#{;pzgS*uA<#XK|S)1=Z4aDK|=KSoM^lV0|-Mp*dKT21VAE0@|=KcfkeLHIl<-* z5^n;}36+N+v5|F7I9CG%ilhZ5TJr{$2*p_h`~!Cpq0;(b=5Q$RHiA}ZfdQWO!ye+D zdHV1@XhUfJ+(zvlNZf^D{BdlW90MQ%8UGM9!eBZ_=1&^UtP2udCg+5L7sgXO^w(gF z;Ir0%wnu>yggNIDiD-;kT=;3|;Qbi>P#ROfH!x`iT+IPk0I&pL1pt*XfMJeOXv0`S zUv0-2#H|^`sK!6@zIqZgtg~=#c;WygeBtmv-XC3p14I_{Il&+b67BNmg#2BQSTa8+ ztbO5gI5mUtFeuGuAbZfQ1>m{}UGYlU?oOwPr4YHog=VJRuAn^nn^N;5=w3qwmH>%Qx6TRk`yhd3 zeNF)Ve=By)Fy;d&jXCg&33_e;TrB}u0k8&O0|1pbf@y$O2*>;gLvuVh*DlEf&8fXO zCx)9q!nFr89=ql(<~M>f&%e!r?4#8`Vy`H%fOI_`)*r{k89?GA$2nmv4H7Wrb0W+b zBv|dvi5Edw*;qAiv0|Y#i-3cm<4eGG8Ne3+D*#pjpk9n(>7f-Ov5qm&N-^i!{n?;7 z$KrEhs}&@Y`>-6bYR0fgiOw7+{S2}->*r$EF|Yw*iU9kM=LVNS;+XrK2$BN{Cbe@S z&kQ7PI-V0fq1Zu~HDlO`P#R0%%^&pK3bh6ndgE_Ap3snTucuO2OxeD3 z`<&>o1PM!*bK*+`jxoIEJt00#i5>PcJ`v_c%NAvU_` z;khe zDe0IsR}HWQBg7i`5e01H@e_{6CIQ z^MXX6=pUj+15D@K{gXyBJA*`*?;oN-ArT+Q0^c5rGytZu?gKCcUKp z0ayUA1YiZg8h{M|TL5+d>;X6ca0K84z!`uG09OER0Neq10PqCh1;87C4**{PepnDP zh(GWl0KlIgX9F=jK`SKTXW^n(pZswcZC?(SfUx15NE`+T)YT98gEb%Vt4Ys7RlNgB z>HIpEnjk>{gfA`PPpFQrf<%_Y84-_%X3#{?;lmb`QPPHpR+#To)GQhykGg4uAcqp8 z?i(SLsfob}K48ji6oS9y2z*2WfJ1^HFL-~UUmGE=;UI`17mR@v#+)rS48`w?phVGF zBZN_UbqE4fX$^uF6=03H4RhB)4_hN>pwLLvx(z~^1_@3{0(M^mcCG)#E(8HsiT6WX zMM=3MSkWc62p?<&637C)A#Hb-g&{NumFS9aLj-|0fWz3a0E!}VL&!pdQ3}zBYp6&! zgg7Gj_uU|WKyAnk!9)`Q05m}^yU*sdOk5~qKLiQj`#=bS z0ktQcG< zvS`FrXej8i3nKKlx2+Bk7^*WG@HXs!@phQ-x3_08x)+1!!a{@r>fnuR9vBQY`xwE2 z(oIH?(uDtujgWtdGaJ#L64xaosIU>?pl<*d1lA&;UIgQPfC6f)6{VbxFo5ErUZf+G z>G6mG6Te$S@~O2T$RV@^Xc54l3=Bn*fq03)`&|%eFnCgR2v$nzJ_sZRhZq2$bAHJ7 zq3|;iIxy^*vy3ih0U2SVg0g_-femg&1v07vnuiYX@lUhU04fE&oQ0^s2L2}j5PBfV zU~(2sCRA5G5M}yQ1VlL%w77?Id4b4@`?5tV?IjSu9=QGyV|LtuhA0!fB$pkOXFK#bb* z&_G65P}JpG;41@1iPi!^f;0T%yX&d%{~9C=R9-D20RjIVr0kwQLCWs=PeID*K?T$S z--e(f1T)O1;5pUw_I6NUkK0wgYMfsEyShyu{fkpL>N-zA=-wLO)`1{l~Wj|;(t&;gs+ zF(EKWj2We3jo?Rx u)FA@Ff9(*D3Ti@VL2*#;nh<&ja3$%_atMm689@!j0slEgK@jkh`2P8OQIPnVp@Ty{?E0TC#dpW2rUP;3Mc!LeUsGd8iNFN5nnvSHBq9t zL6cZREV|U8h61tH8e;LmxI?40_^28bqQ&-w4& z`@7%cfA7q)gU2(*vvG7$+~gFQ5K0K~cXxNg5O^(s_ZuJ=EZhVw5(sGpEfxv61zIW* zatE~BM94ou)f2~g=r%zTUX{JFkZOy9+{Zu;&;U}MD}+;JA$;zF9^OLHrL2QVKFIq( zboU}RN>e&h>*+jjLcD9FL|b{bk&{G3%oe7(}TgH+`Xdepbfv z^p$TB8>yUzR3;g@*{gDZgpu^MNEDK@WTAUiE=Do(^m=3xai_0#W6aE71Y;IvXJ)2n zxgn-E@f0F6lbr13YcjI45Zy27K~PGBTHdg%>8>^_Lb)>u-suoREHFj}GU2Uf#0~WZ z!Nnl2f$$(O4tI;k2Nxr-hM$p{K^XHSp`Ej!es$`as0eRTT#PrNXeNyI?;EQVimoWq z9pZFWNBz1WqPn80OqJqr8vjR_o@@wB7km!WwvN#B?T2~7LBFmZe6)!w&Coe;&5OER z%ny^m9R_2J1t6@{G#IXhDP#Gm?Xb2?&yBa&Y$*1glqY5V;mX=~fQLmm;V= zz(MK}1kD#XC}>2W+~r{RBRZGrg0A}fw2&(4;0+CH#og4UtcRH=;8qpf7Gtk%9&=3s6)f@?Yl`F}T5PGFw7LmxPzy(3x1Q8q@oUA)~hkiBKYEEC;qp2#RNOFlqsUn%6m)Re+#T z;~;M*0AGz*u8MH|jk4poKIAfAKYks-)CXdXWxFJ`1nv@}MUomU5=B#zAg!g2`S5W8 zXd%c2N(W_t0>@n{N-jgUNxIco9wmj$t3Q;WaM5@UMm&o^pUc6E=?E_8aq!w(2p;X= zV8`zeI1fwL6yGW7C=I$hdK&5Et32#_D}u#c9LS2PO7WePHd4oRaCrmN47v$w0kwew z&MW>Rr5Spd=@K&v2be<6hYdABx&2RbFk>o$OY=C$U5wztIu5pPLf|Ol;Nv|A5{{Z` z&Av0HFNC1;FY1uGuJJJ84uC_O-8;5rVX1a)s=xll@}Q zxW69h#v43ra0h}tqB-Pzip`uQ`_9OP)X@wsZ-QDtt)Mp0ZBW4Zo=@aChHf|8dmHB- z=h;g~n4w(fGaPK0fxzzK;9wSlaT_`K+ZF^VyEyo^3W4XO*<Qcoe#W#bXN-x zoAeceGqMtLzNn8f!{n7kHLoV?8-3~C>Jx0gN9@T z$xAu7>qfA8GY65sLQq=C!T3W6YU?ZsqVK#V)f{x5`gf$w?L4gDJ_4ne8gjn7pK23* z4VDt>$betH#h@jirJ!Y?Oi;kN{;6fUp+~9iaAWvScy`@b70M+&&q2p*1nx`@!dD|G zd5?qG5_Lm_@uxqizX|>j8gpwf;#$4x6nq!ed?n~S{|Zv6ljrUH7QxUkYsmSjC~F7x zomWe#qYYf%0^J7P0kwlVKmq497u2bS?ywg3HKxb#?Azn5P%bBl1KTSIigP#^^#+2P zA`WJ~kDzfs2YH79?7m>7zDw3JOVD@ORSX*PH4i%8gqeJnA8qc6W(MueRNJXHp41zGH@nSxL%U(9nDh!{frQTtC zwO!@RF1R8!XFHp$`O8_P#irmhkJ!0IOK4)lwdX2WKWfwF!ytK1mWf)$31&BnDwtDY zHqs{znP*6esC`h!`fHc=u(4Fu*oQ1nIFg7hYPG4AEJa-f4_jFEsS(=5CiaB(c_oz1 z)V_j2^2}=rlpSTnf5ghoEECHDbM}-Su0>R{QKl>?K{cDsMr!M-Sv<>vFnGuz&SxO~ zbTykqv$vmRPia5ZSscsmStQTG;yXGUOx;?=S!kAYF1UwL8Z?VG1{`81WafrwTuA62 z+{kdP{}C4MK+}Rs$O&TYoCTfMgCyc1p1DE8Q;fnRtV(99&@eFagHU?z2}sZRp7fzF zHHiis<2N$PflQoU>)C25T>*Po!B+Ixub2j%)}00WxoE$?_{o2{?|Y7&l36YUpNEBn z!O&j2!iH(RKVx>s%I~x<_>TLevpqG86`!#vGg}Gi$UK9B>6OCk>w;HKY-A}^(b5`O zA`E*Q*#Jtlx<R5e;?qdHt@yvh6#2Pe#5QRBgcJ8 zw-za0&-i1Ce`D3x^XjSSohXm>dM3nBhxfpQMB1fwb+Oshto_{2=4c=LSrj!x$hh6z cjCzw~&$d{%3ARi!0VXo2%3lE7{};sk2Mrsd#Q*>R