From b9e7c32ffe56180a6f2721572fa6e422ac17fec0 Mon Sep 17 00:00:00 2001 From: Dominik Stadler Date: Wed, 6 Apr 2016 19:50:08 +0000 Subject: [PATCH] Bug 58648: Fix handling whitespaces in formulas, unfortunately blank can be the intersection operator as well, so we need to try and skip it as whitespace if intersection fails which can lead to hard to track bugs or misleading error messages with some formulas git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1738033 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/poi/ss/formula/FormulaParser.java | 16 +- .../xssf/usermodel/TestXSSFFormulaParser.java | 220 ++++++++++++++---- test-data/spreadsheet/58648.xlsx | Bin 0 -> 9195 bytes 3 files changed, 193 insertions(+), 43 deletions(-) create mode 100644 test-data/spreadsheet/58648.xlsx diff --git a/src/java/org/apache/poi/ss/formula/FormulaParser.java b/src/java/org/apache/poi/ss/formula/FormulaParser.java index f2430a90b..8e5d8e999 100644 --- a/src/java/org/apache/poi/ss/formula/FormulaParser.java +++ b/src/java/org/apache/poi/ss/formula/FormulaParser.java @@ -1530,11 +1530,19 @@ public final class FormulaParser { while (true) { SkipWhite(); if (_inIntersection) { + int savePointer = _pointer; + // Don't getChar() as the space has already been eaten and recorded by SkipWhite(). - hasIntersections = true; - ParseNode other = comparisonExpression(); - result = new ParseNode(IntersectionPtg.instance, result, other); - continue; + try { + ParseNode other = comparisonExpression(); + result = new ParseNode(IntersectionPtg.instance, result, other); + hasIntersections = true; + continue; + } catch (FormulaParseException e) { + // if parsing for intersection fails we assume that we actually had an arbitrary + // whitespace and thus should simply skip this whitespace + resetPointer(savePointer); + } } if (hasIntersections) { return augmentWithMemPtg(result); diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaParser.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaParser.java index c7c466cf6..83dabacb9 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaParser.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaParser.java @@ -23,6 +23,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import org.apache.poi.hssf.HSSFTestDataSamples; +import org.apache.poi.hssf.usermodel.HSSFCell; import org.apache.poi.hssf.usermodel.HSSFEvaluationWorkbook; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.ss.formula.FormulaParseException; @@ -31,25 +32,14 @@ import org.apache.poi.ss.formula.FormulaParsingWorkbook; import org.apache.poi.ss.formula.FormulaRenderingWorkbook; import org.apache.poi.ss.formula.FormulaType; import org.apache.poi.ss.formula.WorkbookDependentFormula; -import org.apache.poi.ss.formula.ptg.Area3DPtg; -import org.apache.poi.ss.formula.ptg.Area3DPxg; -import org.apache.poi.ss.formula.ptg.AreaPtg; -import org.apache.poi.ss.formula.ptg.AttrPtg; -import org.apache.poi.ss.formula.ptg.FuncPtg; -import org.apache.poi.ss.formula.ptg.FuncVarPtg; -import org.apache.poi.ss.formula.ptg.IntPtg; -import org.apache.poi.ss.formula.ptg.NamePtg; -import org.apache.poi.ss.formula.ptg.NameXPxg; -import org.apache.poi.ss.formula.ptg.Ptg; -import org.apache.poi.ss.formula.ptg.Ref3DPtg; -import org.apache.poi.ss.formula.ptg.Ref3DPxg; -import org.apache.poi.ss.formula.ptg.RefPtg; -import org.apache.poi.ss.usermodel.Cell; -import org.apache.poi.ss.usermodel.Sheet; -import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.formula.ptg.*; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.ss.util.CellReference; import org.apache.poi.xssf.XSSFTestDataSamples; import org.junit.Test; +import java.util.Arrays; + public final class TestXSSFFormulaParser { private static Ptg[] parse(FormulaParsingWorkbook fpb, String fmla) { return FormulaParser.parse(fmla, fpb, FormulaType.CELL, -1); @@ -63,25 +53,25 @@ public final class TestXSSFFormulaParser { ptgs = parse(fpb, "ABC10"); assertEquals(1, ptgs.length); - assertTrue("", ptgs[0] instanceof RefPtg); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[0] instanceof RefPtg); ptgs = parse(fpb, "A500000"); assertEquals(1, ptgs.length); - assertTrue("", ptgs[0] instanceof RefPtg); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[0] instanceof RefPtg); ptgs = parse(fpb, "ABC500000"); assertEquals(1, ptgs.length); - assertTrue("", ptgs[0] instanceof RefPtg); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[0] instanceof RefPtg); //highest allowed rows and column (XFD and 0x100000) ptgs = parse(fpb, "XFD1048576"); assertEquals(1, ptgs.length); - assertTrue("", ptgs[0] instanceof RefPtg); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[0] instanceof RefPtg); //column greater than XFD try { - ptgs = parse(fpb, "XFE10"); + /*ptgs =*/ parse(fpb, "XFE10"); fail("expected exception"); } catch (FormulaParseException e){ assertEquals("Specified named range 'XFE10' does not exist in the current workbook.", e.getMessage()); @@ -89,7 +79,7 @@ public final class TestXSSFFormulaParser { //row greater than 0x100000 try { - ptgs = parse(fpb, "XFD1048577"); + /*ptgs =*/ parse(fpb, "XFD1048577"); fail("expected exception"); } catch (FormulaParseException e){ assertEquals("Specified named range 'XFD1048577' does not exist in the current workbook.", e.getMessage()); @@ -142,8 +132,8 @@ public final class TestXSSFFormulaParser { ptgs = parse(fpb, "LOG10(100)"); assertEquals(2, ptgs.length); - assertTrue("", ptgs[0] instanceof IntPtg); - assertTrue("", ptgs[1] instanceof FuncPtg); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[0] instanceof IntPtg); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[1] instanceof FuncPtg); } @Test @@ -296,16 +286,14 @@ public final class TestXSSFFormulaParser { // Create a formula parser - FormulaParsingWorkbook fpb = null; + final FormulaParsingWorkbook fpb; if (wb instanceof HSSFWorkbook) fpb = HSSFEvaluationWorkbook.create((HSSFWorkbook)wb); else fpb = XSSFEvaluationWorkbook.create((XSSFWorkbook)wb); - - + // Check things parse as expected: - - + // SUM to one cell over 3 workbooks, relative reference ptgs = parse(fpb, "SUM(Sheet1:Sheet3!A1)"); assertEquals(2, ptgs.length); @@ -317,8 +305,7 @@ public final class TestXSSFFormulaParser { assertEquals("Sheet1:Sheet3!A1", toFormulaString(ptgs[0], fpb)); assertEquals(AttrPtg.class, ptgs[1].getClass()); assertEquals("SUM", toFormulaString(ptgs[1], fpb)); - - + // MAX to one cell over 3 workbooks, absolute row reference ptgs = parse(fpb, "MAX(Sheet1:Sheet3!A$1)"); assertEquals(2, ptgs.length); @@ -330,8 +317,7 @@ public final class TestXSSFFormulaParser { assertEquals("Sheet1:Sheet3!A$1", toFormulaString(ptgs[0], fpb)); assertEquals(FuncVarPtg.class, ptgs[1].getClass()); assertEquals("MAX", toFormulaString(ptgs[1], fpb)); - - + // MIN to one cell over 3 workbooks, absolute reference ptgs = parse(fpb, "MIN(Sheet1:Sheet3!$A$1)"); assertEquals(2, ptgs.length); @@ -343,8 +329,7 @@ public final class TestXSSFFormulaParser { assertEquals("Sheet1:Sheet3!$A$1", toFormulaString(ptgs[0], fpb)); assertEquals(FuncVarPtg.class, ptgs[1].getClass()); assertEquals("MIN", toFormulaString(ptgs[1], fpb)); - - + // SUM to a range of cells over 3 workbooks ptgs = parse(fpb, "SUM(Sheet1:Sheet3!A1:B2)"); assertEquals(2, ptgs.length); @@ -356,8 +341,7 @@ public final class TestXSSFFormulaParser { assertEquals("Sheet1:Sheet3!A1:B2", toFormulaString(ptgs[0], fpb)); assertEquals(AttrPtg.class, ptgs[1].getClass()); assertEquals("SUM", toFormulaString(ptgs[1], fpb)); - - + // MIN to a range of cells over 3 workbooks, absolute reference ptgs = parse(fpb, "MIN(Sheet1:Sheet3!$A$1:$B$2)"); assertEquals(2, ptgs.length); @@ -369,8 +353,7 @@ public final class TestXSSFFormulaParser { assertEquals("Sheet1:Sheet3!$A$1:$B$2", toFormulaString(ptgs[0], fpb)); assertEquals(FuncVarPtg.class, ptgs[1].getClass()); assertEquals("MIN", toFormulaString(ptgs[1], fpb)); - - + // Check we can round-trip - try to set a new one to a new single cell Cell newF = s1.getRow(0).createCell(10, Cell.CELL_TYPE_FORMULA); newF.setCellFormula("SUM(Sheet2:Sheet3!A1)"); @@ -382,10 +365,169 @@ public final class TestXSSFFormulaParser { assertEquals("MIN(Sheet1:Sheet2!A1:B2)", newF.getCellFormula()); } } + private static String toFormulaString(Ptg ptg, FormulaParsingWorkbook wb) { if (ptg instanceof WorkbookDependentFormula) { return ((WorkbookDependentFormula)ptg).toFormulaString((FormulaRenderingWorkbook)wb); } return ptg.toFormulaString(); } + + @Test + public void test58648Single() { + XSSFWorkbook wb = new XSSFWorkbook(); + XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb); + Ptg[] ptgs; + + ptgs = parse(fpb, "(ABC10 )"); + assertEquals("Had: " + Arrays.toString(ptgs), + 2, ptgs.length); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[0] instanceof RefPtg); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[1] instanceof ParenthesisPtg); + } + + @Test + public void test58648Basic() { + XSSFWorkbook wb = new XSSFWorkbook(); + XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb); + Ptg[] ptgs; + + // verify whitespaces in different places + ptgs = parse(fpb, "(ABC10)"); + assertEquals("Had: " + Arrays.toString(ptgs), + 2, ptgs.length); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[0] instanceof RefPtg); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[1] instanceof ParenthesisPtg); + + ptgs = parse(fpb, "( ABC10)"); + assertEquals("Had: " + Arrays.toString(ptgs), + 2, ptgs.length); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[0] instanceof RefPtg); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[1] instanceof ParenthesisPtg); + + ptgs = parse(fpb, "(ABC10 )"); + assertEquals("Had: " + Arrays.toString(ptgs), + 2, ptgs.length); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[0] instanceof RefPtg); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[1] instanceof ParenthesisPtg); + + ptgs = parse(fpb, "((ABC10))"); + assertEquals("Had: " + Arrays.toString(ptgs), + 3, ptgs.length); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[0] instanceof RefPtg); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[1] instanceof ParenthesisPtg); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[2] instanceof ParenthesisPtg); + + ptgs = parse(fpb, "((ABC10) )"); + assertEquals("Had: " + Arrays.toString(ptgs), + 3, ptgs.length); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[0] instanceof RefPtg); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[1] instanceof ParenthesisPtg); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[2] instanceof ParenthesisPtg); + + ptgs = parse(fpb, "( (ABC10))"); + assertEquals("Had: " + Arrays.toString(ptgs), + 3, ptgs.length); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[0] instanceof RefPtg); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[1] instanceof ParenthesisPtg); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[2] instanceof ParenthesisPtg); + } + + @Test + public void test58648FormulaParsing() { + Workbook wb = XSSFTestDataSamples.openSampleWorkbook("58648.xlsx"); + + FormulaEvaluator evaluator = wb.getCreationHelper().createFormulaEvaluator(); + + for (int i = 0; i < wb.getNumberOfSheets(); i++) { + Sheet xsheet = wb.getSheetAt(i); + + for (Row row : xsheet) { + for (Cell cell : row) { + if (cell.getCellType() == HSSFCell.CELL_TYPE_FORMULA) { + try { + evaluator.evaluateFormulaCell(cell); + } catch (Exception e) { + CellReference cellRef = new CellReference(cell.getRowIndex(), cell.getColumnIndex()); + throw new RuntimeException("error at: " + cellRef.toString(), e); + } + } + } + } + } + + Sheet sheet = wb.getSheet("my-sheet"); + Cell cell = sheet.getRow(1).getCell(4); + + assertEquals(5d, cell.getNumericCellValue(), 0d); + } + + @Test + public void testWhitespaceInFormula() { + XSSFWorkbook wb = new XSSFWorkbook(); + XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb); + Ptg[] ptgs; + + // verify whitespaces in different places + ptgs = parse(fpb, "INTERCEPT(A2:A5, B2:B5)"); + assertEquals("Had: " + Arrays.toString(ptgs), + 3, ptgs.length); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[0] instanceof AreaPtg); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[1] instanceof AreaPtg); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[2] instanceof FuncPtg); + + ptgs = parse(fpb, " INTERCEPT ( \t \r A2 : \nA5 , B2 : B5 ) \t"); + assertEquals("Had: " + Arrays.toString(ptgs), + 3, ptgs.length); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[0] instanceof AreaPtg); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[1] instanceof AreaPtg); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[2] instanceof FuncPtg); + + ptgs = parse(fpb, "(VLOOKUP(\"item1\", A2:B3, 2, FALSE) - VLOOKUP(\"item2\", A2:B3, 2, FALSE) )"); + assertEquals("Had: " + Arrays.toString(ptgs), + 12, ptgs.length); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[0] instanceof StringPtg); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[1] instanceof AreaPtg); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[2] instanceof IntPtg); + + ptgs = parse(fpb, "A1:B1 B1:B2"); + assertEquals("Had: " + Arrays.toString(ptgs), + 4, ptgs.length); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[0] instanceof MemAreaPtg); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[1] instanceof AreaPtg); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[2] instanceof AreaPtg); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[3] instanceof IntersectionPtg); + + ptgs = parse(fpb, "A1:B1 B1:B2"); + assertEquals("Had: " + Arrays.toString(ptgs), + 4, ptgs.length); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[0] instanceof MemAreaPtg); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[1] instanceof AreaPtg); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[2] instanceof AreaPtg); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[3] instanceof IntersectionPtg); + } + + @Test + public void testWhitespaceInComplexFormula() { + XSSFWorkbook wb = new XSSFWorkbook(); + XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb); + Ptg[] ptgs; + + // verify whitespaces in different places + ptgs = parse(fpb, "SUM(A1:INDEX(1:1048576,MAX(IFERROR(MATCH(99^99,B:B,1),0),IFERROR(MATCH(\"zzzz\",B:B,1),0)),MAX(IFERROR(MATCH(99^99,1:1,1),0),IFERROR(MATCH(\"zzzz\",1:1,1),0))))"); + assertEquals("Had: " + Arrays.toString(ptgs), + 40, ptgs.length); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[0] instanceof MemFuncPtg); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[1] instanceof RefPtg); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[2] instanceof AreaPtg); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[3] instanceof NameXPxg); + + ptgs = parse(fpb, "SUM ( A1 : INDEX( 1 : 1048576 , MAX( IFERROR ( MATCH ( 99 ^ 99 , B : B , 1 ) , 0 ) , IFERROR ( MATCH ( \"zzzz\" , B:B , 1 ) , 0 ) ) , MAX ( IFERROR ( MATCH ( 99 ^ 99 , 1 : 1 , 1 ) , 0 ) , IFERROR ( MATCH ( \"zzzz\" , 1 : 1 , 1 ) , 0 ) ) ) )"); + assertEquals("Had: " + Arrays.toString(ptgs), + 40, ptgs.length); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[0] instanceof MemFuncPtg); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[1] instanceof RefPtg); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[2] instanceof AreaPtg); + assertTrue("Had " + Arrays.toString(ptgs), ptgs[3] instanceof NameXPxg); + } } diff --git a/test-data/spreadsheet/58648.xlsx b/test-data/spreadsheet/58648.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..9f2f7bf47975a440a7f9803b844f105bbcedf6fa GIT binary patch literal 9195 zcmeHtgEjG>Gh+94bf7LmMr0}gAi|&j-}YbJ0+pKMs)$^8UD>BN*~WJa z_Do~V)}Huk;_o5YiPYR#Y1+m{hweH^nqy0$8#XO89bTzemx>z-RO?Dgk^9s8LUaf} zcyPX(WZCBm_$C^SsOVE{xCf59Q$T{_1U;X5h|DUuXhCmRbV^6RLq-+`iTrS8N%W{V zGH}LFu7ubQ=b&pTAW_&GQ4M6rHBfMa&C}?v*-`1^SFt(cs~+2QqF=65Mv^-lty^a3 za~ScAz>24$7rE1cq-`Kmg0vE$%mt#7PCA1eIZuM=lgJa)f2iP9trS1jSJMRCoj(`H z{x*D5?dh_6Nij^uG{`)aZ8zU;C~|gpdYva!)`wzq`RyiQT3p?={0Z>dpA=&3t3xz> zKH9_RjyFd#blGVr6)q$h+2HbIPSp=p?3G~!vXzu!=V1lbXFqH%?%O& z{13`DYH{BC4v#|>I3uy(lr?dMIJj}J|2Y1Ss{h6O{G02Q$q&`~IkCfc6)q!&PG(mU z@uZZ!Bo*3eG#@`vT*hsTE2bk~>1L$B(+{FWpnZY$(&ZTl~0O&b;G<*D1Ude~avonFYqNS`*d3g1n+|0gAE#4%%4jBeP z2}L-O*wY~W!H0V9%}=WlW~DT~RfX3y3l!|6OlAkprI&8s5sws7`8xgd?vR_g^+L7( zkOR%p8JU)bjgW1Pd7i5)#|vMoayQ48J3e1OSi$&=I^HIR4;?7u40>916Ank=g#@ z3<5m8!Jqx_-YSzv?FKlpWlzGcL#Dc1xC6l9E>=^IG99xYKXFbKR53?02-w`Ue{mal2>_u<{jf>cV;ESByM2l_vh2jV14_#AAai=HwsKsXpBOV2b zr#4eZ7bZ;H*teJ4@rsFDO(lKABI4eAGl(J+)Wx8^XTX>Q+=N_=XB|^aN~K)rj_i-8 zlhpB%cIAkpN)HgcesOnGP%pIAgh0-sA|IRIGgzoGo!zzBjtSeBd3FBv*knBy^rm;H zett_F-u3^Ep^7al&q#P&bifB%T=*0482Wqk#3n04mpHLs?;;(8kXH!wPD!On>*D z^|&_;5d0kDPo>*mTQUuW%UqHCrmKWv$m#CEmR{(%oPk-2ZSxLNrCQ5%T&r6s-ot+1 zX=4bvLEZ4qB8PVz;UAsmW@F|GvD9#PwRN&~`@wsMgkC#%B@@0YwQ82xZe?V1KY1X8 z`unHC2NLt3l^&})E31;OfZ`Iu=;vz}H#Z`&E00Tn)tYoxo-M|F=mSQud-)(r@sm+6 zkA=J~nSxvU@ zRWOr)ZKCzU{<4`u##W10bY%jcVXDl8UnN}T3blw=ZmOI8Hp!^SAO*rke-hpohLu?XJ3fEAh*IvVtw0&7`ACa zY*Vx6x5(BlSu41+Oo+iInrJq|-j#)A7Q!!m^g*|w$!!MZmgxBl^BeF8+96=gLV4zvWPXTQ~0njCIdG3R;cejBdcv& z30B^h50(RibWizLvO|muVZw$5ma$CA&ND`)FyqC5o6Z;;c^Sb$oOwonG`d)e$z&@8$a=5vWqerWIWU|L6{W3g%cLXY%P;%Q(Y64kq-*g4T&Ey1dD9tbrJ}K-1TvtLh*9jP?LB@NFgZC~sSdtVv6F11 zqDvdAKU%Ias~-!iX+Q&DJX1&<0LGB!6#z9|(A2qAqUzO3frRg|>0_nqb&h?FQLabD zO^oj2+o0VW*FQjH@Gn+8a5-h7=b3UBM}Aq2>T~w>B4aX7kRxC0fzRIe&0aM|T&g_L zQ?5e@39QhvoV@GwxL&;bdj4!vE5}8O6A|}LDqB$cVX-@@~jAIKw ze~1?gN>D8V$K)=FA#Ra7dt}KLi@F2gB6f;jxeB3$_F|e=G?P)oZ)@A|^@_yjp?6+r z^m2CXhonp_wXI)e<_mmsi)0G!tJXMg@_0tpLfo4%xYidLi&I_rffJ7F4ZzxPONnqf znyR>C1#BO^d334rxcTo;AxAmKVx0Y?s8C`=b*wXlQaKWi;#P^;@sEI4nz*MA@RQ;; zduc)2E$qaJ+xJVyw&S_B__OWr$Lc7b-^C;LTfA@wW)Ep5N@q_73}nV*FPJe(?T?K- zrAvyxdO{@?MY|vW1HnDgylT}J-q!q#HD2de zs@*F$osXe@8IvZBb5eK6x{RwT8vQyvwja=>QbwY@x-hJIFQlTjLCIV07`A9o-Q-%2 z&VCpd9F$$EupoG!fC>l%$=IMOUnVb7sQAEqM{-FwY7TV9?UZBH&a^Kge3q>@9A4`) z53krVP*OyBv=dh@nl;mKyEmo_NCC8Avt6F$o}CXG;Gm>0qzwzrsX(z9W?2A4 z1f!U`)@?McPM|yrBW=4I>KRmws$~^?eNs7_*u$N-398rCAB_3t;yje@_;M4DHk&;x z)(#|SO{aBLBDX1GL3x<7QEE_y>@3VLRbMI+lq+sH$WsPlpX-cNT|0H4oBkRW2!cE< zF$|5A*Xv39Y-xu_5Tp^2+_F8g_Od*PK}njh`4K7dS{3~qK+k7sNOF3dZetiZAvDjk zN>)y|9VKkCq3>MtY4-zN{W_s%7t~}_BGSTYP->>y0_|i33IZH7i4xW<3q-~&78--O z)fZ9sGmNej)Mvr(;y<%Woy8W#_@L_0wiOk_w7K5E4!EQx1f&IXMlg;|CflslX1QIn zY^t|Zq_s9HZHZ?D1cvP^-=&oGGN(8-I)1RPvS)3?!JHLVzPxy0QrrX^D_|gnj?}#U z+A;ggGad_(4S$x+#b6xxPN4%fhU@vfLR%GY)T4yi|7ctKMz}ZJOF~pi5G_xmSBrUV zNjlneDkGQ9x(?yxkWtc0;M&O3=g+p^9|otd25B4x>)^Jbbg%n``MxP9)RzR`_XtTQ zZW=u?GY6_0V%ANviwPza#awR0*sY!d2sjd!*<*DLEq9QV)qy%xrb-{bQJGR87P-CD zhw(ATc{aW&Yw$%8cbLP9Q*AUtugKM(#}bk3oNC_{mdE_s%na&YtRgNiQGNbFM@+H% zJ=5Du*Vd17`bX8PZTTD)rkhXwH*25lF&noB!c+FY%gzNsUsh)Lw50{E(B1hXow>RD zI6!`uE@Rpc6J|K^I`8dM6|t_%WIc0KGMJ`bbb6LX6#7d>#GUlJK>ttSojo3$lQOEOR-YcXDs|R=(Zt-RQ19Iz zTiF;1$nWq6>e#96+r`wn0)lmTyec$>j;@%O%)`s`n7AT1!VyE^kq~RBLOlOlE zT}W~skkT$UqHCm!p@h=-7-bkU=tdFh5h%TVZ`z78T1rgLPN%O?(1sPkBx0KV3gj)v zeVElp80JCvrsr^9gxfh4`tPS+A_79)8ljS4mF3 zCiLE{jGc+yNC;G2Qx`i1?xm!0`@H3pq_2UmVaPPkK5SJXPp<@$WcRXt&IrE(Yv2Z0QgoL>8)XiB^=%w zv9ATG&El~%H9R~n1>Kg;yL{v&Cpaw@?zK}Fd_g4lJl*os zwP=2KF6-n>mjKQu3#edEl0eiaWezg-PoofFjPu@govluvL3;~hS({*I!78Q>#X2De z>BqB+662m`+u7uZHeRi2=fxkS2~mxO(WRh*QWj8AG8tpCu&a+Yg5n9&W&L930x+`= z5rqNMd}lW0=>4gC($zSmJNh(=HJAM~m(mQE#~ktp|aTupIS%XI~A~M_>!BW1vZkk$RISS&l{iE zr8Jo(WT~_Uk!xM2w&oVo8OKs!b}@bPYCK1DF0c!tjMt1@}0(aQ$`ev(2c$JoptukJzC778x)*(L8mLo zlm!BY5o=x`A~|Ds7%rltT0B#U*%yI%9?|zW6fo3v$BATG5#QE8@6+>3Z<&%X<^o1b!oj%^0{pzh z;m_CG$C)|FAeB?bF_%6}om!iFbt!B_M|%#O9gr^sEc+3Og`j;it0_+NPcH^*sx+$rHKjQlHn2jWu4b7w ztR3@%Pmb=kFeNrv^by+ZcAAlh)S|sI!lw|9TpDDC_0Pg;*4ATo0nbCnY7r;tPbj>4 z=3`~trIfg6$D6DCo)D7QfeSkOc?(Y`zw|PDH5TeHsA`=RV?c#p<<^yu3`_+$&)PUF z@!QrvkQ-9le|>(|7-(m>?WBVUeyZFXiUK1y_~h-25mV86_@!pU5#?ZwIyB(uw?6`+w!*J9A6Bx4jc3$O@KQBu9XvAW)T_Fj{osL7c^4m{Asnxum@ z`TEKkmHv)TuVyBzzoMz>XAjDf=Z^`~4v95itMy`wpe?dEs|jquLn6 z7&2^(VsRUaTkHVqj&dR=f-;*z>m4--k>ZL9{RH7Kd)AG=W75iema)IL=4KoLu4-b*3dfQY=`2g zdRPPbaQ^vGRPw-hZKECm@6y`9$G?RMiJbHhN@7WgvS$4h{BY(Wd3_yaYx5l6kV*SrESkvs_k? zv~JjCW$GqN+ZfmX)bN-FL0R4RQ4=rQkl~&it&?<(9E;NPjMcb!%7BLpSLz{m{n?{s zI+3pM?;M4^7QHJ{#p*B}UZ+hYx#uP+8cY`6s>M)52&xi!vuP3ji7)Oj8sZnmX-ZV7 zF~ua0;boJZ%+-W&e`KizO}eGII}j*IDC+TWgk*wv%;He5dN7>OkRJz?;2}P{kg_z< zo_wcP(sfX0mPG2XV9AWLBpI0jC>XK<2hYU5p?is;5qnQM0SsVcC=`o zk69{>eVzA6LsGOfNKPcvf$Sp=xbvukI9y;)Gyu@rX8bAtz`e6R>%-P__8WNT{4qcv zAhN-=*x&zh@K+c9b^gr{2w>%Z0{n9U^e@97=Xy9#{Z3vdAY zC*W;4^)|rmDeEtQ6u2M@Z;)RT*xRPJ3-e#5pNVce^Q%n1jq=ZO=NAS5Fi#2q{Hyr6 zZGJmz{Q>|}{QQW2s#sKx9Q_H=s$=4UyuMm9vt*P9{rgJz{)5; TG6-BRxC5|+lfZ@g$JPG<7hZI) literal 0 HcmV?d00001