From 3395d3abb3b526c41685f51ebeaef1f92434595f Mon Sep 17 00:00:00 2001 From: Adrien Loison Date: Thu, 22 Oct 2015 10:54:11 -0700 Subject: [PATCH 1/6] Increase max read bytes per line for CSV Specify a bigger value than the default one to support long lines. --- src/Spout/Reader/CSV/RowIterator.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Spout/Reader/CSV/RowIterator.php b/src/Spout/Reader/CSV/RowIterator.php index b6f897c..f8e33e1 100644 --- a/src/Spout/Reader/CSV/RowIterator.php +++ b/src/Spout/Reader/CSV/RowIterator.php @@ -13,6 +13,12 @@ use Box\Spout\Common\Helper\EncodingHelper; */ class RowIterator implements IteratorInterface { + /** + * If no value is given to stream_get_line(), it defaults to 8192 (which may be too low). + * Alignement with other functions like fgets() is discussed here: https://bugs.php.net/bug.php?id=48421 + */ + const MAX_READ_BYTES_PER_LINE = 32768; + /** @var resource Pointer to the CSV file to read */ protected $filePointer; @@ -147,7 +153,7 @@ class RowIterator implements IteratorInterface { // Read until the EOL delimiter or EOF is reached. The delimiter's encoding needs to match the CSV's encoding. $encodedEOLDelimiter = $this->getEncodedEOLDelimiter(); - $encodedLineData = $this->globalFunctionsHelper->stream_get_line($this->filePointer, 0, $encodedEOLDelimiter); + $encodedLineData = $this->globalFunctionsHelper->stream_get_line($this->filePointer, self::MAX_READ_BYTES_PER_LINE, $encodedEOLDelimiter); // If the line could have been read, it can be converted to UTF-8 $utf8EncodedLineData = ($encodedLineData !== false) ? From 8fd606ae4fb3f8969b4d382e7129ce3c2b496f79 Mon Sep 17 00:00:00 2001 From: Adrien Loison Date: Fri, 23 Oct 2015 10:52:12 -0700 Subject: [PATCH 2/6] Highlight XLSX over CSV in documentation --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f413d9b..88500dd 100644 --- a/README.md +++ b/README.md @@ -62,8 +62,8 @@ Regardless of the file type, the interface to read a file is always the same: use Box\Spout\Reader\ReaderFactory; use Box\Spout\Common\Type; -$reader = ReaderFactory::create(Type::CSV); // for CSV files -//$reader = ReaderFactory::create(Type::XLSX); // for XLSX files +$reader = ReaderFactory::create(Type::XLSX); // for XLSX files +//$reader = ReaderFactory::create(Type::CSV); // for CSV files //$reader = ReaderFactory::create(Type::ODS); // for ODS files $reader->open($filePath); @@ -87,8 +87,8 @@ As with the reader, there is one common interface to write data to a file: use Box\Spout\Writer\WriterFactory; use Box\Spout\Common\Type; -$writer = WriterFactory::create(Type::CSV); // for CSV files -//$writer = WriterFactory::create(Type::XLSX); // for XLSX files +$writer = WriterFactory::create(Type::XLSX); // for XLSX files +//$writer = WriterFactory::create(Type::CSV); // for CSV files //$writer = WriterFactory::create(Type::ODS); // for ODS files $writer->openToFile($filePath); // write data to a file or to a PHP stream From 8ef6bdac623f8479f84894149387c0bb021c77d4 Mon Sep 17 00:00:00 2001 From: Adrien Loison Date: Fri, 23 Oct 2015 15:45:30 -0700 Subject: [PATCH 3/6] Better date support Although Excel has a Date type, older Excel versions use numeric values to store dates. The value represents the number of days since Jan 1st, 1900. The only way to tell if the value is a number or a date is to look at the styles.xml and check if the cell has date formatting. --- src/Spout/Reader/Wrapper/SimpleXMLElement.php | 16 ++ .../Reader/XLSX/Helper/CellValueFormatter.php | 64 ++++- src/Spout/Reader/XLSX/Helper/StyleHelper.php | 224 ++++++++++++++++++ src/Spout/Reader/XLSX/RowIterator.php | 8 +- .../Reader/XLSX/Helper/StyleHelperTest.php | 134 +++++++++++ tests/Spout/Reader/XLSX/ReaderTest.php | 46 ++++ ...et_with_different_numeric_value_dates.xlsx | Bin 0 -> 30377 bytes ...eric_value_date_formatted_differently.xlsx | Bin 0 -> 32960 bytes 8 files changed, 485 insertions(+), 7 deletions(-) create mode 100644 src/Spout/Reader/XLSX/Helper/StyleHelper.php create mode 100644 tests/Spout/Reader/XLSX/Helper/StyleHelperTest.php create mode 100644 tests/resources/xlsx/sheet_with_different_numeric_value_dates.xlsx create mode 100644 tests/resources/xlsx/sheet_with_same_numeric_value_date_formatted_differently.xlsx diff --git a/src/Spout/Reader/Wrapper/SimpleXMLElement.php b/src/Spout/Reader/Wrapper/SimpleXMLElement.php index 4892aca..0e3d758 100644 --- a/src/Spout/Reader/Wrapper/SimpleXMLElement.php +++ b/src/Spout/Reader/Wrapper/SimpleXMLElement.php @@ -151,6 +151,22 @@ class SimpleXMLElement return $doesElementExist ? $this->wrapSimpleXMLElement($realElement) : null; } + /** + * Returns the immediate children. + * + * @return array The children + */ + public function children() + { + $children = []; + + foreach ($this->simpleXMLElement->children() as $child) { + $children[] = $this->wrapSimpleXMLElement($child); + } + + return $children; + } + /** * @return string */ diff --git a/src/Spout/Reader/XLSX/Helper/CellValueFormatter.php b/src/Spout/Reader/XLSX/Helper/CellValueFormatter.php index 79f92e7..3c417ca 100644 --- a/src/Spout/Reader/XLSX/Helper/CellValueFormatter.php +++ b/src/Spout/Reader/XLSX/Helper/CellValueFormatter.php @@ -25,19 +25,35 @@ class CellValueFormatter /** Definition of XML attributes used to parse data */ const XML_ATTRIBUTE_TYPE = 't'; + const XML_ATTRIBUTE_STYLE_ID = 's'; + + /** Constants used for date formatting */ + const NUM_DAYS_FROM_JAN_1_1900_TO_JAN_1_1970 = 25569; // actually 25567 but accommodating for an Excel bug with some bisextile years + const NUM_SECONDS_IN_ONE_DAY = 86400; + + /** + * February 29th, 1900 is NOT a leap year but Excel thinks it is... + * @see https://en.wikipedia.org/wiki/Year_1900_problem#Microsoft_Excel + */ + const ERRONEOUS_EXCEL_LEAP_YEAR_DAY = 60; /** @var SharedStringsHelper Helper to work with shared strings */ protected $sharedStringsHelper; + /** @var StyleHelper Helper to work with styles */ + protected $styleHelper; + /** @var \Box\Spout\Common\Escaper\XLSX Used to unescape XML data */ protected $escaper; /** * @param SharedStringsHelper $sharedStringsHelper Helper to work with shared strings + * @param StyleHelper $styleHelper Helper to work with styles */ - public function __construct($sharedStringsHelper) + public function __construct($sharedStringsHelper, $styleHelper) { $this->sharedStringsHelper = $sharedStringsHelper; + $this->styleHelper = $styleHelper; /** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */ $this->escaper = new \Box\Spout\Common\Escaper\XLSX(); @@ -53,6 +69,7 @@ class CellValueFormatter { // Default cell type is "n" $cellType = $node->getAttribute(self::XML_ATTRIBUTE_TYPE) ?: self::CELL_TYPE_NUMERIC; + $cellStyleId = intval($node->getAttribute(self::XML_ATTRIBUTE_STYLE_ID)); $vNodeValue = $this->getVNodeValue($node); if (($vNodeValue === '') && ($cellType !== self::CELL_TYPE_INLINE_STRING)) { @@ -69,7 +86,7 @@ class CellValueFormatter case self::CELL_TYPE_BOOLEAN: return $this->formatBooleanCellValue($vNodeValue); case self::CELL_TYPE_NUMERIC: - return $this->formatNumericCellValue($vNodeValue); + return $this->formatNumericCellValue($vNodeValue, $cellStyleId); case self::CELL_TYPE_DATE: return $this->formatDateCellValue($vNodeValue); default: @@ -138,14 +155,49 @@ class CellValueFormatter /** * Returns the cell Numeric value from string of nodeValue. + * The value can also represent a timestamp and a DateTime will be returned. * * @param string $nodeValue - * @return int|float The value associated with the cell + * @param int $cellStyleId 0 being the default style + * @return int|float|\DateTime|null The value associated with the cell */ - protected function formatNumericCellValue($nodeValue) + protected function formatNumericCellValue($nodeValue, $cellStyleId) { - $cellValue = is_int($nodeValue) ? intval($nodeValue) : floatval($nodeValue); - return $cellValue; + // Numeric values can represent numbers as well as timestamps. + // We need to look at the style of the cell to determine whether it is one or the other. + $shouldFormatAsDate = $this->styleHelper->shouldFormatNumericValueAsDate($cellStyleId); + + if ($shouldFormatAsDate) { + return $this->formatExcelTimestampValue(floatval($nodeValue)); + } else { + return is_int($nodeValue) ? intval($nodeValue) : floatval($nodeValue); + } + } + + /** + * Returns a cell's PHP Date value, associated to the given timestamp. + * NOTE: The timestamp is a float representing the number of days since January 1st, 1900. + * + * @param float $nodeValue + * @return \DateTime|null The value associated with the cell or NULL if invalid date value + */ + protected function formatExcelTimestampValue($nodeValue) + { + $numDaysSince1970Jan1 = $nodeValue - self::NUM_DAYS_FROM_JAN_1_1900_TO_JAN_1_1970; + + // Fix for the erroneous leap year in Excel + if ($nodeValue < self::ERRONEOUS_EXCEL_LEAP_YEAR_DAY) { + $numDaysSince1970Jan1++; + } + + $unixTimestamp = round($numDaysSince1970Jan1 * self::NUM_SECONDS_IN_ONE_DAY); + + try { + $cellValue = (new \DateTime())->setTimestamp($unixTimestamp); + return $cellValue; + } catch (\Exception $e) { + return null; + } } /** diff --git a/src/Spout/Reader/XLSX/Helper/StyleHelper.php b/src/Spout/Reader/XLSX/Helper/StyleHelper.php new file mode 100644 index 0000000..52d1844 --- /dev/null +++ b/src/Spout/Reader/XLSX/Helper/StyleHelper.php @@ -0,0 +1,224 @@ + FORMAT_CODE */ + protected $customNumberFormats; + + /** @var array Array containing a mapping STYLE_ID => [STYLE_ATTRIBUTES] */ + protected $stylesAttributes; + + /** + * @param string $filePath Path of the XLSX file being read + */ + public function __construct($filePath) + { + $this->filePath = $filePath; + } + + /** + * Reads the styles.xml file and extract the relevant information from the file. + * + * @return void + */ + protected function extractRelevantInfo() + { + $this->customNumberFormats = []; + $this->stylesAttributes = []; + + $stylesXmlFilePath = $this->filePath .'#' . self::STYLES_XML_FILE_PATH; + $xmlReader = new XMLReader(); + + if ($xmlReader->open('zip://' . $stylesXmlFilePath)) { + while ($xmlReader->read()) { + if ($xmlReader->isPositionedOnStartingNode(self::XML_NODE_NUM_FMTS)) { + $numFmtsNode = new SimpleXMLElement($xmlReader->readOuterXml()); + $this->extractNumberFormats($numFmtsNode); + + } else if ($xmlReader->isPositionedOnStartingNode(self::XML_NODE_CELL_XFS)) { + $cellXfsNode = new SimpleXMLElement($xmlReader->readOuterXml()); + $this->extractStyleAttributes($cellXfsNode); + } + } + + $xmlReader->close(); + } + } + + /** + * Extracts number formats from the "numFmt" nodes. + * For simplicity, the styles attributes are kept in memory. This is possible thanks + * to the reuse of formats. So 1 million cells should not use 1 million formats. + * + * @param SimpleXMLElement $numFmtsNode The "numFmts" node + * @return void + */ + protected function extractNumberFormats($numFmtsNode) + { + foreach ($numFmtsNode->children() as $numFmtNode) { + $numFmtId = intval($numFmtNode->getAttribute(self::XML_ATTRIBUTE_NUM_FMT_ID)); + $formatCode = $numFmtNode->getAttribute(self::XML_ATTRIBUTE_FORMAT_CODE); + $this->customNumberFormats[$numFmtId] = $formatCode; + } + } + + /** + * Extracts style attributes from the "xf" nodes, inside the "cellXfs" section. + * For simplicity, the styles attributes are kept in memory. This is possible thanks + * to the reuse of styles. So 1 million cells should not use 1 million styles. + * + * @param SimpleXMLElement $cellXfsNode The "cellXfs" node + * @return void + */ + protected function extractStyleAttributes($cellXfsNode) + { + foreach ($cellXfsNode->children() as $xfNode) { + $this->stylesAttributes[] = [ + self::XML_ATTRIBUTE_NUM_FMT_ID => intval($xfNode->getAttribute(self::XML_ATTRIBUTE_NUM_FMT_ID)), + self::XML_ATTRIBUTE_APPLY_NUMBER_FORMAT => !!($xfNode->getAttribute(self::XML_ATTRIBUTE_APPLY_NUMBER_FORMAT)), + ]; + } + } + + /** + * @return array The custom number formats + */ + protected function getCustomNumberFormats() + { + if (!isset($this->customNumberFormats)) { + $this->extractRelevantInfo(); + } + + return $this->customNumberFormats; + } + + /** + * @return array The styles attributes + */ + protected function getStylesAttributes() + { + if (!isset($this->stylesAttributes)) { + $this->extractRelevantInfo(); + } + + return $this->stylesAttributes; + } + + /** + * Returns whether the style with the given ID should consider + * numeric values as timestamps and format the cell as a date. + * + * @param int $styleId Zero-based style ID + * @return bool Whether the cell with the given cell should display a date instead of a numeric value + */ + public function shouldFormatNumericValueAsDate($styleId) + { + $stylesAttributes = $this->getStylesAttributes(); + + // Default style (0) does not format numeric values as timestamps. Only custom styles do. + // Also if the style ID does not exist in the styles.xml file, format as numeric value. + if ($styleId === self::DEFAULT_STYLE_ID || !array_key_exists($styleId, $stylesAttributes)) { + return false; + } + + $styleAttributes = $stylesAttributes[$styleId]; + + $applyNumberFormat = $styleAttributes[self::XML_ATTRIBUTE_APPLY_NUMBER_FORMAT]; + if (!$applyNumberFormat) { + return false; + } + + $numFmtId = $styleAttributes[self::XML_ATTRIBUTE_NUM_FMT_ID]; + return $this->doesNumFmtIdIndicateDate($numFmtId); + } + + /** + * @param int $numFmtId + * @return bool Whether the number format ID indicates that the number is a timestamp + */ + protected function doesNumFmtIdIndicateDate($numFmtId) + { + return ( + $this->isNumFmtIdBuiltInDateFormat($numFmtId) || + $this->isNumFmtIdCustomDateFormat($numFmtId) + ); + } + + /** + * @param int $numFmtId + * @return bool Whether the number format ID indicates that the number is a timestamp + */ + protected function isNumFmtIdBuiltInDateFormat($numFmtId) + { + $builtInDateFormatIds = [14, 15, 16, 17, 18, 19, 20, 21, 22, 45, 46, 47]; + return in_array($numFmtId, $builtInDateFormatIds); + } + + /** + * @param int $numFmtId + * @return bool Whether the number format ID indicates that the number is a timestamp + */ + protected function isNumFmtIdCustomDateFormat($numFmtId) + { + $customNumberFormats = $this->getCustomNumberFormats(); + + if (!array_key_exists($numFmtId, $customNumberFormats)) { + return false; + } + + $customNumberFormat = $customNumberFormats[$numFmtId]; + + // Remove extra formatting (what's between [ ], the brackets should not be preceded by a "\") + $pattern = '((?sheetDataXMLFilePath = $this->normalizeSheetDataXMLFilePath($sheetDataXMLFilePath); $this->xmlReader = new XMLReader(); - $this->cellValueFormatter = new CellValueFormatter($sharedStringsHelper); + + $this->styleHelper = new StyleHelper($filePath); + $this->cellValueFormatter = new CellValueFormatter($sharedStringsHelper, $this->styleHelper); } /** diff --git a/tests/Spout/Reader/XLSX/Helper/StyleHelperTest.php b/tests/Spout/Reader/XLSX/Helper/StyleHelperTest.php new file mode 100644 index 0000000..3b8edff --- /dev/null +++ b/tests/Spout/Reader/XLSX/Helper/StyleHelperTest.php @@ -0,0 +1,134 @@ +getMockBuilder('\Box\Spout\Reader\XLSX\Helper\StyleHelper') + ->setMethods(['getCustomNumberFormats', 'getStylesAttributes']) + ->disableOriginalConstructor() + ->getMock(); + + $styleHelper->method('getStylesAttributes')->willReturn($styleAttributes); + $styleHelper->method('getCustomNumberFormats')->willReturn($customNumberFormats); + + return $styleHelper; + } + + /** + * @return void + */ + public function testShouldFormatNumericValueAsDateWithDefaultStyle() + { + $styleHelper = $this->getStyleHelperMock([]); + $shouldFormatAsDate = $styleHelper->shouldFormatNumericValueAsDate(0); + $this->assertFalse($shouldFormatAsDate); + } + + /** + * @return void + */ + public function testShouldFormatNumericValueAsDateWhenStyleIdNotListed() + { + $styleHelper = $this->getStyleHelperMock([['applyNumberFormat' => true]]); + $shouldFormatAsDate = $styleHelper->shouldFormatNumericValueAsDate(1); + $this->assertFalse($shouldFormatAsDate); + } + + /** + * @return void + */ + public function testShouldFormatNumericValueAsDateWhenShouldNotApplyNumberFormat() + { + $styleHelper = $this->getStyleHelperMock([[], ['applyNumberFormat' => false]]); + $shouldFormatAsDate = $styleHelper->shouldFormatNumericValueAsDate(1); + $this->assertFalse($shouldFormatAsDate); + } + + /** + * @return void + */ + public function testShouldFormatNumericValueAsDateWithBuiltinDateFormats() + { + $builtinNumFmtIdsForDate = [14, 15, 16, 17, 18, 19, 20, 21, 22, 45, 46, 47]; + + foreach ($builtinNumFmtIdsForDate as $builtinNumFmtIdForDate) { + $styleHelper = $this->getStyleHelperMock([[], ['applyNumberFormat' => true, 'numFmtId' => $builtinNumFmtIdForDate]]); + $shouldFormatAsDate = $styleHelper->shouldFormatNumericValueAsDate(1); + + $this->assertTrue($shouldFormatAsDate); + } + } + + /** + * @return void + */ + public function testShouldFormatNumericValueAsDateWhenCustomNumberFormatNotFound() + { + $styleHelper = $this->getStyleHelperMock([[], ['applyNumberFormat' => true, 'numFmtId' => 165]], [166 => []]); + $shouldFormatAsDate = $styleHelper->shouldFormatNumericValueAsDate(1); + + $this->assertFalse($shouldFormatAsDate); + } + + /** + * @return array + */ + public function dataProviderForCustomDateFormats() + { + return [ + // number format, expectedResult + ['[$-409]dddd\,\ mmmm\ d\,\ yy', true], + ['[$-409]d\-mmm\-yy;@', true], + ['[$-409]d\-mmm\-yyyy;@', true], + ['mm/dd/yy;@', true], + ['[$-F800]dddd\,\ mmmm\ dd\,\ yyyy', true], + ['m/d;@', true], + ['m/d/yy;@', true], + ['[$-409]d\-mmm;@', true], + ['[$-409]dd\-mmm\-yy;@', true], + ['[$-409]mmm\-yy;@', true], + ['[$-409]mmmm\-yy;@', true], + ['[$-409]mmmm\ d\,\ yyyy;@', true], + ['[$-409]m/d/yy\ h:mm\ AM/PM;@', true], + ['m/d/yy\ h:mm;@', true], + ['[$-409]mmmmm;@', true], + ['[$-409]mmmmm\-yy;@', true], + ['m/d/yyyy;@', true], + ['[$-409]m/d/yy\--h:mm;@', true], + ['GENERAL', false], + ['\ma\yb\e', false], + ['[Red]foo;', false], + ]; + } + + /** + * @dataProvider dataProviderForCustomDateFormats + * + * @param string $numberFormat + * @param bool $expectedResult + * @return void + */ + public function testShouldFormatNumericValueAsDateWithCustomDateFormats($numberFormat, $expectedResult) + { + $numFmtId = 165; + $styleHelper = $this->getStyleHelperMock([[], ['applyNumberFormat' => true, 'numFmtId' => $numFmtId]], [$numFmtId => $numberFormat]); + $shouldFormatAsDate = $styleHelper->shouldFormatNumericValueAsDate(1); + + $this->assertEquals($expectedResult, $shouldFormatAsDate); + } +} diff --git a/tests/Spout/Reader/XLSX/ReaderTest.php b/tests/Spout/Reader/XLSX/ReaderTest.php index eb42b84..c774749 100644 --- a/tests/Spout/Reader/XLSX/ReaderTest.php +++ b/tests/Spout/Reader/XLSX/ReaderTest.php @@ -113,6 +113,52 @@ class ReaderTest extends \PHPUnit_Framework_TestCase $this->assertEquals($expectedRows, $allRows); } + /** + * @return void + */ + public function testReadShouldSupportNumericTimestampFormattedDifferentlyAsDate() + { + // make sure dates are always created with the same timezone + date_default_timezone_set('UTC'); + + $allRows = $this->getAllRowsForFile('sheet_with_same_numeric_value_date_formatted_differently.xlsx'); + + $expectedDate = \DateTime::createFromFormat('Y-m-d H:i:s', '2015-01-01 00:00:00'); + $expectedRows = [ + array_fill(0, 10, $expectedDate), + array_fill(0, 10, $expectedDate), + array_fill(0, 10, $expectedDate), + array_merge(array_fill(0, 7, $expectedDate), ['', '', '']), + ]; + + $this->assertEquals($expectedRows, $allRows); + } + + /** + * @return void + */ + public function testReadShouldSupportDifferentDatesAsNumericTimestamp() + { + // make sure dates are always created with the same timezone + date_default_timezone_set('UTC'); + + $allRows = $this->getAllRowsForFile('sheet_with_different_numeric_value_dates.xlsx'); + + $expectedRows = [ + [ + \DateTime::createFromFormat('Y-m-d H:i:s', '2015-09-01 00:00:00'), + \DateTime::createFromFormat('Y-m-d H:i:s', '2015-09-02 00:00:00'), + \DateTime::createFromFormat('Y-m-d H:i:s', '2015-09-01 22:23:00'), + ], + [ + \DateTime::createFromFormat('Y-m-d H:i:s', '1900-02-28 23:59:59'), + \DateTime::createFromFormat('Y-m-d H:i:s', '1900-03-01 00:00:00'), + \DateTime::createFromFormat('Y-m-d H:i:s', '1900-02-28 11:00:00'), // 1900-02-29 should be converted to 1900-02-28 + ] + ]; + $this->assertEquals($expectedRows, $allRows); + } + /** * @return void */ diff --git a/tests/resources/xlsx/sheet_with_different_numeric_value_dates.xlsx b/tests/resources/xlsx/sheet_with_different_numeric_value_dates.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..1f565c17bc623bd3b78c0ba58cd0ae8e08313d77 GIT binary patch literal 30377 zcmeFZ2UJsCwF1_rU8If%U@wdI@fOLW-6xp??1S+C@AZGM z28!=@pKFunIJ0Fy*7+=BG*y0zL3t&zi|g2Vt>*T7?L%@mrUodQ-06oe#9Y`v-JZ}D zuzJ9&^WJ#pf;TJ8g9>p$KGC$BC66yuA03~>A8m3ypy%|g@6+k`)e+t*4fPMDtA*vC zdMAo|Ij;vlb{>x8v_8R}cDRaJPj{P@bt}&{fgap&4{Vci6CHclbuP+8g550fLCQEG zwmMks{-^St>9?O=xU`0r9OGoXAAEi$lkXJa6n^3Ztd^WvobypErQ`>Cqoyq`?rXF5 zqtZ$qkr#ZmpK`=p0yyy%^6j5gGqAO$xrWk1M(+H!c-gZghzcS_7tY}rot@(8-0r;UzpHWZ& zBaaB|BFV607^v9iaFc;H;HONB|o_X)9jZTfd5WKw6Kc7v_>tPkY@bI=VpF2+H z2(zJ^TybZg8CT9@%%@RO-P3nN&AqT`2e!|fp)!m8@1qW7enNj$5-U#_(%=d~>Q|CY z^Ny~a$j+Td$Ofv<*Pcup{vzE}*2-_?$$r)C{FdmuqeT`a=(QnCM~8&5O&H`K|Li?U zX&t_6kg95-UIBoC2JJ2Pn*#--{JpNAP+ogU?ssRRfifNBY5!+m#dmu>+vHg~SECxD z`osJNl8@^>ap=q$l?er$rTpBl2paF)tI2+!W0D(~eu*VJx@lD+cjdd=!FIkQOHo#4 z`^6d7kBrR(Gihav)=6-*PuOYFrZOE78yl`som4%jG;jKO+u(2=+I;RIq2j*F!IX>y zj{f;`f&)b-yNBKv^=Jo6s=EG^92|fxDVn~nVH%j3iE=M^c#pcp<})dXVf-4P+sk)Y zjHL9LM)#>jT=ASK-y_b?U%MB0W0kR`5o! z>hx^%+CT=E#7llrr#B_f-rfh7(=94n-odYW78G-ehw}>0UMzFm9~Cqz;Z?=}B+2;+ zo~{vZU$b=oYE{ime>gcPgYMdN4ETxBa>p`0XMe6={gDjAD8A^D9R$my(%rvb&{9~N6OjMEa<0vmUX*6cJF1XV z^Rq8zrB{W1ln8zLTC3O@`_|!>w)6X>R_nF1^j1pM0fobDx3~M5nU+3QlljeSG||gXq*=f@=uK*%3b6WRP$iL(Dza zoHz5H3yZxsxc(3^5dXDMY2r)Q38Rst$s^;pzK&-NNEae~bw&wDi_yF4%RbzeQ=a84 z2YF+-kNY2A?YTOh`J#_-eI6aEm2>Y3Utd_(0kP9MgV^s$Qz}*4jQ?u#`;uNUW1vaD z34M5eoBRNGCx2HLi$H%5U$=n0aKX~8&D1K-ajL&zPN?;pPaZR;$HWtfJ{U{S80}rd zcPx1|s@=VW-sKvXwOrZf<#B7Dn>kR*Vl$fcS*e4pNs|7}mmX=54KCZK#M$WfplvjC zhp*RIRvXZ{zDP#DtJ{oJD_^y=m0;nzRp}Dy_uZ5E2z?zHGRS{6ReAP!FHSfB%n0oj z@V`to(B0L?RqogQ_#U(0Y)nytXLy=KNkV#|C})Z50{THO#$MaG59+%*dfG#%M{D^&AN1zePa91#XclOD@7f>(^6FuJo`35 z^`h>T$WvN(GT$7~HF}mQC8ZGUBrUggUySJ6ekN(ZMgr-B*_>#fV8w%BBiAtTP9;XO zNN2N9H>JEo+F>OMV;>#_^J^S2TDp1rR+gp7LB=Q{;S0*A&$(tY2FBYcM%;)c7n!D9 zjNngWa!`s&_{nTpU0OQVb2k1tUAg$%(x2U*6>j2MUOci$f_LN(`SZ$ELpqdXp+E*+9m=5riqQgI7-I_x z)k(6RYeRLz(j4nM{Bi(j>s2@H5KN?ZAwfUBKj;kU=@2w)clVc%8=+abuDC+y(mu{A zhk?xrK48(((N^IZe^$s((v|s8#gClJV+L4Vtov!7dl>1Rh?e#xphYA{xaUkFjZxps zB7r+%=t$tHv_!9gyU5|7K>E}JiEX^CFj;^NgwK3Q%FWM}2 z%j2wAtw~CsR%(l?PLkKj1MzEy>!N3c7!Tg`O+i2 z8LY>#y_7^icNx4SN_ z9XP#mts<1Y>crZO1|2qgF&^5h!i1gZlS&R<+%eACAGA_z9ue)V&uEE;T5zWM2|X7X zaBe-Z#B%Gt)+6bDq5i zzn#aw;`b$L)xYNSx?($x1an(hDwo8Rn`&fT;J1fL#vHp>4{N-xKlkQdQ^Qd~rz^EA zm4Ss8T}n6XC$I8sS3kk|O#0GKzKZ{uFyHz7Y6HWToOi!E`tCQ*qJ*E~OFnVoletk; zOq|1oZob)cw-Wx^%9-0^4b|f}x|mgGL_b)YnjCm5-ZNv@x=-m_YfhSC+&S}u?E?(q zOGdGb2dAtri$>8}APvuQHQcIExhlah0^wCjXWZdR3gu zc2c@4zT*(u}Hk+1pn<-JnwpEIaPh4L#E7yxwb2LRT8moRhRO9 z3wi%g2gvn~O#k%2;RaqKXX#5$ueM45KcZ9pMMEQ?{utU zG{V5s_vH_f!Sei+-nVn5Y}Wkmy+dA`P4vA5EUo%EdsN1pegF6F(xD zuUSrFcCql%6t_{l(-~taBbR;3Om21EaxqkS&90eQe!=wF>)qtp5cd!GP%>vnGtvsI;D7K$R+JKL=q3wq)c zzZGPf{CImWShz`QMfWA1L{?P$Q8i?2vz$hL{ZVd+E!6OH?W^SjK?grHW@$ei%rc7_ zZa$|z+!|i;s_K}znf4KMK}_G4wEk=ktwQ8WyCUPL2N6$ibi@!oOnOK5-#<*H7xin)vdEKzP?ensiQdba)o+8Bjx7{pWJBsdG*`5 zZ_C4EX++)VN}}t=eUH2m!n&O-6SMXRTS}Pq;1l7RO3Ad)&ov&pet8i2FCDC9^@@8R zw33RX0|5Tt9Bgkj72xjb8W+4sYnsmVj1ud&6S{~GvA6vko=u@;8O!7P**p}GV zY*sj@T;4BV@g&`M0y{gds?!k=TF~KDVDMkRe!fA>KxeQcVC>1JQu~$3Jt<(4t6UX^F zR^B^)qSs$V7rbA*61ktecx8?Q1{N~Nb#=M(m0ng&D1H?C%=`Np4^Ng`$>&dZR44yD zvgLTn+cWB=#r^hUigF06oRewo<}D(ZRN~_u2bd|PfG%pkCMCN9X?E>>=|iV81)sAn-*b|jR-7y+{G@dyTqjMH+S=V& zNswfDDz+9}#EvbObC?{&u5`BuYwd(CiX}k z{0Wnlos5z09ummY`6YVS<&Jks-C?$R_l&by*uoD01w*LsHZ0yC7%#`8iH zrPBpIXq`>^_T-_L$17i7Yr;?Z3yQC*O7fMu7_Ucn)YDb0+`6z&utRA4@uvBKj_BO{ z&J9N{)}9x9lBXW(d1x?OeB37|DGJ)bT3cH#c@QWrFZ))557KF$$V}3oCmrTDS&>L` zYjIa$?n)e2Fd&G40U94GperUE^g*n`KEl++gj1l@^-0xrte1C zeC1748=KCw`nvZX95~pRQjD$DOlwMtX*N01XWdU;zCAR2&d^2JLYb08C7PW6-k<04HHU(UYYF=&7s*zYiq zv4=`s0Zy2?qXJO@?x<@bin8*+iPMHAw0jhx>lZBk3*;CQN*YfE%4ruP7$1HEPlr?S z@Hd7!I*w-Mrh0~F&p?6z0KCZQ+BNTeA^_my8|ZJYe_F)a##RJA4)J*a+SC>Rn4O#h zu4$W@o!z6khx?26-)@^Dzjy}*W&U;l-;n?94`vtF0B1<}y>l;=OMtTv1fv0f&fEE# ze;@$Re}>vm1P5M&VE91@=JAIZgkbqS*zGr1cMrbu8*Kdx=YqKogtNyho3p!<3k2_N z9Y|mKOS{`&;NN(FFd#bmuD-7R9?l|rx_~U|LYk{Zt+K)i+w;->J+7Jxc+&-?W{(2XG z!{4V7;(oyjf_b5K7mq+g$fWmZ?hEm~a?TWjp=bByd7+H==(b-GdDNK_6xmaNT0B{TH1Ocbz@!h3LTiJ)MlqAXor`U(idZxBq!@8YDp zr(0PF)&eYPoB&q<1zdsd&VVnl3y1*bfIe^<`o0GELp@gk55OC0afNzap%xDsR=^8t z``378zi@tg=1&Z_Ur#UWJq?ZWuW^1GVeub0C|W*Rd0Iv2qfUF4R+IJw?MXm{7D;=G zR*O~_YCTD-{s#wUf8kREa03=0PJg502LwSQ@9|*?oW3m(B>BfPB9Z=%9?rd~M2MR| zr9}PN57GHUVu)wdUo`*(KpOa6I+?%fsS8uj|3{C=FzuU5gDhPb!1UkKFWqOl1-fDA zK2A6F=ks)vbW_mZIG_#9fcGzsd?2noe$7O{A9ylECAb zs_$)qKYR8x;Tm^t^BfQ<{b#HEsQiljuzaq3zWnmv#`u%s-?Rj~2djcLz}~`YVIKhz zSTn2z)&%Q>mBH%%p!u)o{x>Rr%>6HU_uBTP-J6Bq$NQ_UT>)>l(`UXXF>#_Dczyr7lapD04L%9BsR`)dQ z3*3OH{?$+Z!6~gU?QvSee`B>L_H6lgF6htD>(XljBJ{`TPtqT!H`;@L_dI$DsP80n zKl2Cozj@9d7=M$%^)FbER{o$P^ULb?Y<2JH>k!}G5Z|tWu4pKC0Duna+6{jXH}^mh z1$lW@5p5`yxr!M2I?GCnIC*=E{OSr2@plby^}p`wA`9&0!M*1IV9oqjexu>-{j)95 z1OPO@LouuG&ou0in%eis0C?@*~bc~t7|Q;7Z)05p43n*mM+AcF>g(QpF$IB8&Rvk$5!IO(_!9oMGkKJNrS z?8l>U^G+^<=;_xj`_21_Vv1M%BN>@^`S=eAh#!%VJbFw?Sw;24Ni`kaGkW?4hDH__ zE?Qba;yJsxUUhT#@C*nHx*m+a5fT+06B`$QE8*_F`w!C6A3n;+dz$~Ops?uqi}H%f zs_L3IZ);oI+B-VC-o5`YFgP?kGCDRsfx{C%fB8DUu((9}v9`Xkxdr~*-ji!j&fl-! z1cUhgt6Y#0X!g<4!f4@pa?$JyhCVP(TDn8W>AAGe!=3!N4=dbc;5mIK_jL=SsG>P> z{}um!CSEZmoH%Juv|p0_#{`S~Pf7NNV1LRr3Fz#Vj{i#+6(aJ#L}2e>>NHePiclv2 z78q2Ha>6(PBtUtF(P-pWnHu-+c8>ruf5oiN`;@ zg38zTj|Oc$5sJ;kJu&|=YdTmjn(yc^ZSdM|Vq#&l!c;ox6pd)IO}vmz%6>lSZ!d~H zVj%q4ZAu-y-4w1pXPSttV2@lStKuxYNa`IE9lh~SBj0N*qwD2VW4UH^)w#dbRI5ch zh}GS*THv^J@8kR@D}#-z4jED&(@o88vj}2gBM%kmS0kb*+}kKBaM<$l zKew6DPywb;oarui89f!iAq%L$4{_jM;K5t7YcLaKZH_cxXlr(bX=@nF3S1_H_5@h< z9Uj!;vL>HV!^VUQdpHtrgRum&KHkypl(zQm^%+*s{z#cx%_cWl$3A@AVc8&=AGX9E zWs7Q_O?G6Y2$5}xvu#+$wx<{!Vt5R`&R9L0-Q(qr0+li+)}E2nB8jn?6 z&gBxi-6!qRFWy;3Xv$G|wpz2dB^+3(Kr7aS3Vb#KkT_G!Q7VuZG&f6@Sf8cn=D_|1 z)*2!i%c6B~VHVyfxin#VlPpu{nc)_pVINlL$!jYU(=h#Dr8Fi~xoWzk-SWMdD(~81 zW0u#owFWouM@qs5^;=eB#9)x#mRssmxA~x9&LDf&a_Fc)j3%*=a%(gSOUxd`{^W{a z2Q6lqunSMns#Jh+7rNb%1pXOI%VD>wGL;=8w0mDmgKP!{&6Ux5Xq_PHw@o|S6F)se zMv+gx=x}~v$FHkuTDUafI+E^xh^@E6tolubxkmRxmlz>_*qz;IL|e8Xg~8K7lJx!N z9EuN}s0)^mj9X+%CP2lO>myqN`mq{TH#~4-qYgxeCmwT1VPm$NM>5jG)rd!)6rkN>-0#--G5KDsT>gA|@D;yF{v3Aee^%6gOr1!zz(gtlM@Mbef*r(3JUmqcPDGvs zy$IOoZN63nG&|8`vBB?JC(w=^;T%Lt!*g`Onk{syyn*FGmm|2*Z5E>#8fP~m!GgF3 z+K}b@d!=iKm(S)P@@D_Zss7^*bdxfSV5_IYWsg@7YT_}PbE~Wi8Q*UaUv_S1r51Z+ zm^?^(THvV?Lbtw|KB9Wfu1mRUxN_lci^jRKgYm`r9I618+DCa)FSsMVL^(>#j?*w? z@3cqhC))Q1u2^awL%`Ag--S-1N}S*D`q^JPEAhtIPa>_tWas_9@7`ReN7efsQF6;0 zt0VpF(*2FVIK~Jv57Rkk%w@0*M+LaZ!h?-M=!%QO+-)K3F-p4u#^6o4#HYlOTwH@! zYq(ULU43QBYKz*G)ky7QHBV`xoq^2v^H1-bySg=uO`qESp0QGz6B7Pthg*NnR0yL1 z-n>D|#^nSonpNbUDcueUM0-+zGHB;hJ{2J1}5(zb00qZbzE)!w$EP z)K9xY`#LX)-Ro}12 zL{sijfzAbh(nfOJWn07Ikoi=AR12WUq7+G4aR_0=R9;FCLE)ShMe7~=cyrU2>0^S; zsg`5;^X@sciET4SVrZ{{J^18+>J#-RpLQm`O;q7@W>Wb0@!H8NhAm1oYx65Yq?y@G zH)2;TSqI`4^>31$NK%)0pBH;((5KR>A#Y+} z_*sriD!Iku4HF)sMgz`6%F{Z=1FNSr^xK#MLCEN4C(C0|QYmZlj;} zj`7pd2STNH@TmRE0*Q7WwGOxNs|@9sZSO1oNS^zO4lfv5>K!e%abCT}TR(K?)s zogbq~YJB}-VV3=yQjfD$MNPv%RU#|XCn)5{C9UM9oOvX(5f;!oArwP)_pJ1Qkf`qxA-xT-qqYkx2e@?`v%khUC-p{pLzZ z$0XO!r#=I2pgJMtkivVZ0JDZYDYz@^#7Jta#vz}=_rX4SXdR=WjbtM#U^}9;%C;b& z^1$`s;q>#P8agi*Lo_p|r^XkFmmIqMo;%uDpYaO@;Gfj6E6<$pCaSohEZ9J!EPAa& zOXCf==7S{?w27#Q!7c}e>`h*2}pROG$xSpt}k4`BiKQSnA$cQ z<>wKCO_WofWz1g<3o3d~NaX+nnltQ6?})F&Y(9NH<<+-Tjj7))=VNlxRV6SEh1%33JAoRSebJ_~0jZuPm`M zJ5J8F;IdiJv2AB+-wqOns|0mx*KWhO@%k&wIP5qdF1XDgXx9plh;OPQA15RS8xw0H zFsym#>~>yZTF?UbGW7t)nADh4GdiL9hPV|Oh^V;aX(xANWJ9pW!l<8&eCDOrnCs&z z35P$!witx7fpI!uOBp(}V+J|&jlvJYIvhDM$3;Qzw(4sbRKJD9xT8MHwzL^ipR-?C4m_&by33~x=Hi8H34}|Q z2o{WTDf(ki0a<(?2-FCkTcyXC4mGlp^+?!=Ql+qSizu=jaTzas??yg4+lIu2zH+Fr zF%z$;AfwYZ@#)TU`-qU)F2uw;4Ew0ovBh9DR0lFX%@L|Q&{+IdhrJc9H@cqrg+#Q5 zI^Hh(;QPYKnF+7(!^AbiPqX(=OtYosT^Ypb*dmbLG0TimiT*evd9Op5F0qS( zs3GldtjYrejJYPkTPsze-KGp+2Eho}OofFH&mh1=Qk@~lhqL$U+zO!-64RThzyWa* z-E5o|!lO}muwI75eP%dlmU*(*qX=Cv+p(Qe7~q=!A-%AT!ZV^&a%2fVGHWp`lVv$Z z(~Tb7>`83i9_=b)SqkR?cW&TO<4`@8foMl@xZ4J|p=dOF6SR1}YMa`HcoDaTsD`nhns7#Q_f7wOAk~C&2vKUq-t|GoCuH9I@*SB5&{RI z43)4-;k5;$+O?R8d~q9a(8`09g#=89s}^8p&XQW%1AG{(hBk_%+OlQ2t@0$#OY7*4 z@Kc%9O~%Ud`Zw_~Rco*#H@r{4bR~?)8%hO)JZyVgph`hV4p0A}3=C+sm@&I4k+@#Roxe1Z8s`J0N4>XA0JWbos3kzQ8&WEZ zn_(I}4C<4dFQ&A$3CQGmhPcJ8|6E_*h-_u(m`>^~Zv8P~TJy2y4n8^Sh^33UQ6D@5 z)C-k?5$4;Wu$F<1X>u=d>p*a5D9=#=26SP|sG3~cPqKRcq82>cp^lhzJuM@|4xP|B ze>Stj;fS9MrOJQwsr3Wj5kHSBGJs~G1z1?*m7aB={2tD#LukK#LSSNQ^|G4q6E7Fr z=`Vh$ucfb#a!-Z5<0%~pKXCtGAN;u?;n}S(DUPh@*rOszCTa#5MjG;WbBvEw7ndA# zDUbNR-_TX?tu638fFx>Kez#Sy2Vqs2dC@Lz0RJ{!H`2sVMr6li&{`~`wQ6EwlGE>6 zAByv|Zn?rC`UqLM6xH_!4RNWQ8Wp+{vEr#l9^%nSx1MUhXK_}6tc9K`HMU_y36=EfwTa#@Jr$9|6zLAGzUvspv}w3Ab~Y8r3W#jUPZbwiCRrX*df* zdEM^hZn39UKl;<9-l#tpj15Tui0l)_xVRXkr)lgDvKT4$5xz%Vdl*%+Vb?iWf1LQT zefz<13c5S%X*cmuz zK`cC#w&dm7Fd8JsJnLCtXHC|BK5+uB4SAE%Xa}^`Xgmx`bj)&S$u5R;b*xV4pWDr= zcOnV4s#&!?QMDNjLehgzTWk01ndwHj>}!(m?OLexP$%}{&LxVxF0b;r>PXiU#9uzR z@p4zWS>2$K9_`lFw4Wj!-mjXDlno@ntx1u+Zt&U!FZX$35@ab3l4Td<$77nE^ZyS@#K}SP8Od|{=8|nbj{Ym zuHe<6-KCzwOLj|~+Q-DoZA@7Qaw9KX2X2F#BC$HSX^dXQ?xTEBeTEvL@K26Amy5E2 zY{_iJ-FqVuM@$9k?{?FM^OB_RY1j0|93Ggl4o)h|6j*vjm^x{X^FPT$Ge65Mcouw0 z-0E-uhLn=8_nMHZ?}XysuAYmVLqM5pgVy47AmV^N9?9h8^q~IjdIL&#Si1Deou?T? z-}TMdqcP$;`#{0w89wkeak(x02-r6FL=KN^ed!n@%sWDcxy?Dk$p*wDH%l;vr1?(M zr0uU!&&MZgV!eZUld?I;*cZL{lNjYWXd#_}h^<62yN3(B9>{E)@L9=$2jLLo=3P4d zT9RYE1orq~TWq2h4Jf$jN<`jLl4BqrBT_1ebvSqwJfXBoM&eA|xk3H5vDJF*m*_6D z*o|-X+=U(^+iW-IdNhmOn)+h%<^+Vfckf~L=YjV&uMBUXxG-*{cE|Fj*mYyM=xy1z zg&5(=G_O^5G+##(YhWtZveqGDZ;TT1fM+6mB*y8b$EW~nL6a+v_4Kxsro)!*o4T5? zIf14F$g5Dz7e3g?0Q#ZuI;49owdpaK<0R%*PVgSCox)Q-6oiH97JdyyC|(A4$k94v zbB=)t?>w|*`sQ9ECU~#&hHNIjSX8_E{HbS}g`GhSAne9R=H9`DXOe=?f)%+7GxD!$ zQSn&-N%dAGx}K9bd8T?8S}i`c8x229)&e~|{N{QTTSwDJMi2*}?Gbyj_9(`lkeMw2 z*0ifo0TvDQoEguT+|%|SXdaYeTQsz|mKqV@+c_^&9!kIMk!+GhD^#S>XFECHAU1xT z$ECN<-a_&OWhX&Z4D%dAl_~SAf;&Zq^u0!Ac;qD>U2j~rSbOdPZiP>O_?u7V-L1=l zwxi!N^Dq~{0FrYHMFuP*xmgc4%94Z2!7378moU>S(8?!?Ug7k8>!~nz%Wy5?(n3afG9I~q44op?qtLB0s$9l44VVvOudv4`IG~r!>quK& zFB!F@7n%-W6!WBJG2$uRkmc>>d26VK?wkgnHd~H52%t+ftVmjexX%+MyJ`7B(}Hb` zMd*>`R$JLPA2hcqu-?l|`6$dbP#>_Ph90ybAv)O)UJCytEL=W7+3yiD^N8r*-7_56 z8<%wqya>&%a3P+Z88p=)rIq&1HSu|+rH2c~wO>Q4*jpL|Jzs}6ye?PC5e_9F$GqAo z3YEKgfxC|Q`4!Fmg)7`7?o&`DBcnp*&RSTU5grn&?GNXl^0bYM_#Ol;A)1#~B zod*`?OiZc5H?e~HZ8QA+Ri>N^0&zEGm3l10w06!kk*Xt#Ul+#K`{1Qk0^0l*eeysx z1Z#xFVB`ja@V&w)Yj-Z;Nb->+`ZT%-k6VD!( z%~mzb1Qs^S<$x8f+~_(366we!WAHzVssB^dEs>nBH~o^P-^ z?2f^{VR@HAyr}>&NlYy!sZ_%$=dE?=+?~kc@-NlJkD7ZWa+X5O;P{#j$8qP)yHSL# zE=0lvyamZjc72oGffd3yKeb0^8-#9EBnary=~b{ zWH+3QW9P)D?9wsYh2yrMta>V2cafLF$+tDNKz4I>MN>x)9H&>3qfL}B14~{|y0PVj zQB7{MH=z}r)(Ni}wyFWGLzs);Gkh2yp0Bg_!0gg0UdFU_5G~gh;u}XyYcm*JrvjWy zDx0;IHwo-<)mBzkw`3}F5Soirpge8`wR4he2wo&T#r3wW@`453b8g>uaHarge3~SY zfDy!-c-fK8-P+M2+kc?Xl z+BZ)j%+On#LZlzJ8X*@Yt4Ge0jX)oQ z96j1Uj1RZcn-Hk?2!)X;TGDB&<;_+E!iW-bW#)tXFCLngEMZAknNs< zn#!72-Yrwayh0<`f}0WL?45G^K*FZzkcQ}7nb4vowxX%I_aKPC!xMsLc`=8pNv84i z6~q91c5L8|iDq5?%;L<(ER| zY>@7zv9|NE6hYjI)nH}5nB30t8a-r21_$Sr1@`@K=s*JVZjk{ z*p3lpXlCE2vZ020!2}r4xs?rI;KYE<0AgbdKN1oH;*%or9DUt1GE8JRixX0TCYIg4+Y9N>&M@em=& zC3-8P>qCo_>%)=N#GdLhx_mz%EQ+YVslNLRBUMWFHU3#bmLT*v&atl)5(m~cBehfq zzt4lKBq>5orx)TNr5C~Rbuh5Oa_!PUTnqa?>nYIlWc``K#q=4WFkc_$*1jnZj`xQn zFj=!i)6|K`?X7mcTK!gL1TE;+nYHf~QKAhAq$bR8-9cW(Caa-hkuG7vRA4hVgYdo6pbj9qgUBPp&68BGQD%QU&}AZ|IbekR@{ ztAI{L;q4<$Toi$&A3CK|V_Hqnwm>viP`QDLRlVH|t+v$N?jTYP#*#c6PYRpC#dnT} zw<9aDZDlF5EcK@NSw|Ov{bo;aj1sWwq5`qP%1|{*C`)PLX%q)l4exbj ztfqWZP8C?l=p8#WT50^CPf5mdMo8U`)Rc@d#@p*#_Q}YUY-k$5K31-d)otdJP_45F zveFA9O7qpv1(4&nUg(!ow{6PlZjohHanjwj;2A9ohj6;qOwrXh+ zV@r7B@X1g?#ZkXoC^1;P5nVCR(F*ut`kT$%RR!6agDFsx@3>H@A@kV+ZO% zmB(w0^eho#o~fR0$AoML7LpusXBrN+A1wpZ z&kqN6kGWP4DVg%1c~wU-q9Ky7`@4^jE>s{D3#*dj@w3*%!-I+5H}4Fz`WF{g>{DQOJ|(38KOTT7oATnX+&lx|kN_Il z?29g0hAPi{ev|TlG(3Q9-H$^;XU+i~VUN4Taq$DZmNCDbH3@alMNa_$}sPTXfX$)HD8ukYvWA1ps+XRjdiVm$KczTP( zQ^yYND9rTu;O##MBYUIznP)3%7@ z$h9^E@y-&KeUX$`XAW-7NMs=toVtpWw@YQ^HlB!FdXn zECa=$1`8^XHU!+zI!Ohxp_L1EuIvI8_)-N3o2Fprp?&jNP-+@jpbz8e{aSMfRj;8N-73MomA z2O=EJLi+z5O#N3Vhvq$+P3}N89K=k{qCK1_y{X+cfzyVcncsrf?M?P)PM_EQMh65n z{(qwMX7Yu^PEp9ho8}n8B7z>=yQ?1r)50WayK((Wwx&BNy~~ zQUMhAOq5c8u9FZHu10aleN3RBnc;z0Tf`&D;ut8Xf5?DBtkM8-ey|-28i7!Fe4qq{ zPLF(SZ(%&-RS!r|a6jh07x@1zQ*b$GA7;@4RPT^~Ru;ei3z1SL@JQo0Uh*l+CLa@X z_mQ;Moo|tfb;l&XJcJ|;`mY=LzgHcQi*R>LToPs{k`-*1zYqoqmdDXrVc3NAgCjhWePjQ1Jg^~$SLT>E7i8?rlkX@Q;35YKB8$dS#HN0 z<2}?Bi*YK04wY;95;U|!Nm8A>jYs=(iDe108rpM{VS>1_D5oZp$!e^I7tUT^zhYRq z&cscZm~}4zS2K_tvsr@P9v#07?1XlTej#?k$)9ZLuc_Y{*dY5+{7wlyfc_DK8Op{33)y3$J$%a7{b(P|2Iy09?QD7aGDr zq+p66svWt*U9Uq03c51@>L_I>`~aDq=uiQL2ivNzxgQUTdto-C+Mq0~VuH>+Dz;Aa37H$*1bw>D zmflEIcVXv8QIcvr?(BEWN{WoVs5a%pvDB~4LT>+ibo=j$0J{l3pIhM_#FeGW=K8qx z)DW}kl)_QBG|u?(g6qq%jtYz$9!`%G=oh}+BKZBNRd_59hu)d%g3e^hHeXZ|k??eD-*(OBP`JVXMt$d;gFVJD=4-l11Ic0rZ9a+< z+b&HEcs@1eK1z{)Q#0T#8ln!k8y-o%7}oO z#z9T}6j`v&wvKOcUO9=?HGr6CkYs17BKqbHpQ(7Q>La0&_dj`HQ;pLkFG@^moFizz zm)@QX0|;NR3R(+vwUv=&$xsgbv=qT!gMspHt}||w!WRxD%K4yE@X1CQY#(&02o;}A zP&Qqw&4I1V@-{74gF%q*%t9%5+Kgs%HPLZh8lwhz;3|}szZU^J!5wAWyjnt7TriZo zgD(JKb@pPA~Y2 z^pk3#%>rstY%VG|rx?edpJf`zyE{ZP_#dJC|3hK@v*7h$UL#i@Z*N-`P1=yPp2*$# z&a6W7YQO`|{bc%k;5{cu6sZ?sY6aFja;}VaB#gG@?wyGq?y2z4S8M$U{B@VhhPi zL?LKrJVis)HPj8K$u#qQV7@&>S5;+{(_>^=nvdf>`mTj@! zU(8}Ooq*vTJ6DC!lXKrH64lvZWbW#I<=y>d7}YtuCyM&R8o{Mrqg*AK2mx@X^R{4^ zrMqim!fdM+2Qe(U3U91Zad1v}96DhWh7Emz_DnMgrc91gsk{sKGDDFZi>llVPIoP& z8!uR;d_8gh`<;kZIhmVjX6$F6aQy$YckSU&t!;k|X%b^7v7HMciqw>2Mk$Bn(AgnF zNl4R_9HtC|oMNNc*pXwRlpJzOn4C#+j1U@!4Dy;Ov|`M>kJ{f`+3&ut?|Q#)@7*8o zKbC8`p4+q5^Q`;u``yodCs&haq}s@IM+X4L(UyF`AL$NfETcZ846Alk`?#RhKO$xG ziJq2*!zTNi6Z;Dme6vF3o3=}?(>KzFj5vHXlVKyK8XMZZD9nadde}03fCc-}?`WVA z#IR~QtXAD-{E)_Ih<7_nn~#alFB)sGb6g=XJwJv)bOXg>My1T7X%u*SJ{uaI0hahL zxB)UZgtYZiUX!*lm_qXd(c0~>fo+7ssm?93u?F~^BZ~TAs*WDoig#s`APo)xNVKyC zS%OS0n0%MHoxX0C4f&BIyuCcZm*923sfE`mPVM;ircB-UEf+8Dh&INwKUR^Adh#T1 z^K&B%;#1Fp4v`H3q;;x=;YAG&Ez_W)45$tON*8l$LDI}gSol)n8gh`&7T7bzd60gC z)`b%7kTT9UHgW`Bn{l7j z^wCT&dGL$ ztDQ`@cK(u!{j`-r<4FgoSrGua!&?!HeyQYEVIz>*jL-a%zRkIZ4W%eY16-uf>~pH` zvI%Gq#A)%HalDMvH^xP+43i|fEG66~b)Qw6l~gNT-g}?3LFT&t?iCy<5@tW#+?TyP zS4l)0#+f8W?b+Clc`WO#x$VWg_x(o_Esgd+$@@hZjOt2||Kevr?}8tt(OOAcVLyzf z=}o~rSkIO?5>fS1HoV)UM3FWrIpUzM*yO*eVf_bPDS#n^Oh=e!2*APB(_FamK{0o{ zXw_u3=;k@A1Gv@n?AKQKf>?gc&V&6Ys?IAP>)n>O`9m}&)w97k59H6sRVPQrCG+d3 zPe`qF8w?xJXd@YT-DBFd&X{-|hRO8GEoGMXd|E1G+O(Aze0}eBS_QUT!`z)|0w)tJ zo2lc>ed(`u4@t;RU5ULu?IK!`B6aMn4-yW~7$BY*vw$1#6aAmVnTEj&I<`m{SGo6n z*?m+)N#WII)Vaa3Bcrm|&7ntHkq^ms#7I^!-T>@zQQ)>nn}cmRgq@ctlwt}xQJB|2 zzBv*g1T>FMGW}rMm%~u6Bmq8=+IJA_T>UT*oc#j!`kx{NsXAgeOh4{mV7|JQ7 z4&@Qaj&6DVhUa~CZoSlzg@Y~2=*@}URkl*Ji@<&!PB&F4+}j$}K|(3rdF-^q`GoNW zvL~N;29w}$q7b?=#+2>NS>#Sy&xR?9}e=8fZ+gz z+h3DgES)&>2PlKK%}&sNc;Sp@4CgR6=qP`RW9=(4b>_J6xv zq@ZocfO;EyPI-?CD`9<@Z$&zklPf6kt%(2i1(`^)alrEUY(PCD4@EGLP~`l|6Z7Us zfP*$Z@IjHZ2)?lc%0+Za0G@dq0A2&TuIwVz#Ci(HTTP6o@w613?P8p@x^%+2V6ybI z^%I-EytfJsDd_73$wEZOgD~*gR?y%C`2Zb(N;ZTt8^Q+pNWgb8cl`V$EukK}iexn0 z*B80!b)vg;d$eH}Q)@BdB3ErTGv%z%+|-V;hDLQH?N~P5jOrMyqOE$*se-qCvx-@0 zftRh0!U&+bS z{3Wce4A^B|lopE#QdOQU;d}JRMF=y!@PQ5WDJ%tc|IZQt_Q&F(Kak{O9HG;!NUpWi zsZ)j{78e@EPiiS`8n|%OS0q8iGZWk^{z%oa zQU3V|z$O6j`j^fZ@sxCw?isX2N0c}>(YH4@XlSs?Y9{yjb?Q(IrZ32de_GwaPQ+AQ zMn(_;(C&r@Q#uN}=|X_nKi*X#K4FVag+1}^-qfbZK-?BaP>wsbVJ2kMTN97n&q&|q z(w)&pdMy2@Lt5B%iNg9>P)P+c;rvHB?I?lTwdHAbzS#N z{jmShyijODr+%0`kA^P+PXa*kEGH*uK?>kQG%U4+DK`yp`P3B7cRp%h^RuB~EIon3 zhTaDGqVwQE)>W1`Fm>mbo+w@v`YQ$T89$9pQY9c^@9f!f4t3LHkq8YE)@yUfljPtV z?yp`pj9tGr11$04-@9CZ1;dkY6qGix`%2f<9r>^sPEBBU3a%1!1Eo3q+%Uu2)ge^> zMBt6om?Q7)?RMraLElXUDxp_)3|8(^sk9I3A$irdgT(_V+uC^dn33K+ z7KMxPeMIkqa@XFSXNU*X!ks;y*lcuqWsZCnuU1KUhv%HLq^DzrSUV>q1s7wO*fb}r zJ>8cmzTq#Vr_ z2-zPG5O-c8$e!;ac0BB@(%;VjW|J z>LxGlPE1r|=H`oa+)WMdvch6dLCugG;H2ZnDKk6_ThQd1fq>!~lMhv!x$%YYO0tet>lnN7tGOy|N+gV2tKF z4{Qt{qbHTGDX$Nlog@k*o0gb&oG>{lzO{zCsYxkI|M3|+1O@?+?!J_Qh=sWm#rX+_ z48F2B@~YHgRvkS(?m8TO_;Q){y>&@p8v|J}#JCR)4Y@liZlC@nFD&41sf$&2j|?V@ z&F1t}&mr2@!#0c)%uBF?@YDNd;u*Ko%ab0TXK^yjs{3q1=C0(_DUdD{C8M|G;4yel zYf9JK_A+ag8e3GYN-%yXa5kaFvpI`uC(MzSU-{?X*l6Hzl1KK_2N$oN0>&oES4;654jQcq1I;a4JS4Sv zZD0c%>TqZV354wn!b58Hw&A@hkxb`wCvfPBrRWSmqap7z^(O5zEg?ANypDneC71EE zqDSDgVb_MDfJqK~H z&-O_m6vKn^9Gb3KJX>SoQoN_>7~^B;tk`aMmpd&R3mb~cUXBHr5#*=WCpbmPi`}-< zKf*b?mL()#gPxZxvj^+-A}LN20ls=(?b0Ge!M0(g+8NV)*(nXJRYhh1zx2e+HeLZoJP5$l1^jCWx}(RYD; z3cSYPWCiE^1XPzvHu-Z*oTsy%+&Vif#oSbw!!iZl>hGq75M=2vUOr!I{hHWa`=}B_ z5NvH8+I7m}PUt>GMw8e;zF|cAeHo4i=~wHVIBb7_2?ICuKYWitgY^}fMg9s~%AALq zh|;=wddQGMVp=%)iTK3t?xTsVQ5=o_kbc}HXINn0DATO zt4OLn!PQ}@mj&o|_1_jf;I;i6)x6a2DPY>LOmD<%NZncK^u~MeL9-)!(pJ?p=ND)1 zduLI!VXALf7Qadn88L8k(Zgf()Maz`8LqBI{kRa>O`*CU^bO0E+M3R+8%$}qoWXTp z47E|yU$1FmKI=`;`?{)VJgZ=>F{?tZ=Vn5tBgM=)ww z>KsztxP$B1o11n?RKA}QZiJW`RvM&#DsV^hi|e6Q7kK_EyMj&JWiiS+M~k_3*ocfd znR)p2CTsd`@qVPdfqh?*QNl;nJwE<}U6Cz|S$UoP+3G1-{I^_ro~sp=t~=(hOi%Aj z%sKNSr}|FunwtKLZZ?X3FAb!8*QfCZ@wK%)bMay4TBa%*H)_i{-x^JD+xy9kC zQP<ViV$?QK#*@Qe$&Z(?pW30^v1 zS>a${V56vE_OeS9?IM{fHZ1Uqws5D+uP435hPR!4*^z}bRu!iCFgSAdF*?@s+QraJ zJmmN}`SLf3*e74lX9;{zDuM#`=i4bpP)}$@_J`vlIQWzUuU4+83OBAvltIJgJ-;4c zugAT(W)O7Ev#N)W)bU(n5I&tW6Z}!rqcK26ZQwRRxoqfCZ>^W!1*Im_K6H7Z{I5e#gX+h>J^Js;=6{j$$A!kfN;xa?hh@i?Pvv_<{8a=5MV|jg$p4t- m<-@;Mr{4}=b@7|wzbRL9V=k~omU>(AK()Z~zW}vOA?TmOxkDcS literal 0 HcmV?d00001 diff --git a/tests/resources/xlsx/sheet_with_same_numeric_value_date_formatted_differently.xlsx b/tests/resources/xlsx/sheet_with_same_numeric_value_date_formatted_differently.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..8f5a20ce05f86dfecbb3912900a35f70316ee8f5 GIT binary patch literal 32960 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 Date: Tue, 27 Oct 2015 18:34:58 +0000 Subject: [PATCH 4/6] Update README.md --- README.md | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 88500dd..4d0a4ed 100644 --- a/README.md +++ b/README.md @@ -18,16 +18,9 @@ Join the community and come discuss about Spout: [![Gitter](https://badges.gitte Spout can be installed directly from [Composer](https://getcomposer.org/). -Add "box/spout" as a dependency in your project's composer.json file: -```json -"require": { - "box/spout": "~2.0" -} +Run the following command: ``` - -Then run the install command from Composer: -``` -php composer.phar install +$ composer require box/spout ``` ### Manual installation From c4c6dddb20360bf3218fadb42afdf7a8aea5229f Mon Sep 17 00:00:00 2001 From: Adrien Loison Date: Thu, 5 Nov 2015 10:35:37 -0800 Subject: [PATCH 5/6] Increase entropy of uniqid This is to avoid conflicts if two folders are being created at the exact same time. --- src/Spout/Writer/XLSX/Helper/FileSystemHelper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Spout/Writer/XLSX/Helper/FileSystemHelper.php b/src/Spout/Writer/XLSX/Helper/FileSystemHelper.php index bf9e350..22d23e4 100644 --- a/src/Spout/Writer/XLSX/Helper/FileSystemHelper.php +++ b/src/Spout/Writer/XLSX/Helper/FileSystemHelper.php @@ -94,7 +94,7 @@ class FileSystemHelper extends \Box\Spout\Common\Helper\FileSystemHelper */ protected function createRootFolder() { - $this->rootFolder = $this->createFolder($this->baseFolderPath, uniqid('xlsx')); + $this->rootFolder = $this->createFolder($this->baseFolderPath, uniqid('xlsx', true)); return $this; } From 8b666fc6cdeed9c38f36b5a10b10ef5d1640217c Mon Sep 17 00:00:00 2001 From: Adrien Loison Date: Thu, 5 Nov 2015 15:48:26 -0800 Subject: [PATCH 6/6] Fix PHPDoc to work with Augmented Types --- src/Spout/Reader/CSV/Sheet.php | 4 ++-- src/Spout/Reader/CSV/SheetIterator.php | 4 ++-- src/Spout/Reader/ODS/Sheet.php | 4 ++-- src/Spout/Reader/ODS/SheetIterator.php | 2 +- src/Spout/Reader/XLSX/Sheet.php | 4 ++-- src/Spout/Reader/XLSX/SheetIterator.php | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Spout/Reader/CSV/Sheet.php b/src/Spout/Reader/CSV/Sheet.php index a417709..f949a62 100644 --- a/src/Spout/Reader/CSV/Sheet.php +++ b/src/Spout/Reader/CSV/Sheet.php @@ -11,7 +11,7 @@ use Box\Spout\Reader\SheetInterface; */ class Sheet implements SheetInterface { - /** @var RowIterator To iterate over the CSV's rows */ + /** @var \Box\Spout\Reader\CSV\RowIterator To iterate over the CSV's rows */ protected $rowIterator; /** @@ -28,7 +28,7 @@ class Sheet implements SheetInterface /** * @api - * @return RowIterator + * @return \Box\Spout\Reader\CSV\RowIterator */ public function getRowIterator() { diff --git a/src/Spout/Reader/CSV/SheetIterator.php b/src/Spout/Reader/CSV/SheetIterator.php index 7e7af38..4ce0d54 100644 --- a/src/Spout/Reader/CSV/SheetIterator.php +++ b/src/Spout/Reader/CSV/SheetIterator.php @@ -12,7 +12,7 @@ use Box\Spout\Reader\IteratorInterface; */ class SheetIterator implements IteratorInterface { - /** @var Sheet The CSV unique "sheet" */ + /** @var \Box\Spout\Reader\CSV\Sheet The CSV unique "sheet" */ protected $sheet; /** @var bool Whether the unique "sheet" has already been read */ @@ -67,7 +67,7 @@ class SheetIterator implements IteratorInterface * Return the current element * @link http://php.net/manual/en/iterator.current.php * - * @return Sheet + * @return \Box\Spout\Reader\CSV\Sheet */ public function current() { diff --git a/src/Spout/Reader/ODS/Sheet.php b/src/Spout/Reader/ODS/Sheet.php index 012f635..65f3183 100644 --- a/src/Spout/Reader/ODS/Sheet.php +++ b/src/Spout/Reader/ODS/Sheet.php @@ -13,7 +13,7 @@ use Box\Spout\Reader\Wrapper\XMLReader; */ class Sheet implements SheetInterface { - /** @var RowIterator To iterate over sheet's rows */ + /** @var \Box\Spout\Reader\ODS\RowIterator To iterate over sheet's rows */ protected $rowIterator; /** @var int ID of the sheet */ @@ -39,7 +39,7 @@ class Sheet implements SheetInterface /** * @api - * @return RowIterator + * @return \Box\Spout\Reader\ODS\RowIterator */ public function getRowIterator() { diff --git a/src/Spout/Reader/ODS/SheetIterator.php b/src/Spout/Reader/ODS/SheetIterator.php index f8b9203..f8683f0 100644 --- a/src/Spout/Reader/ODS/SheetIterator.php +++ b/src/Spout/Reader/ODS/SheetIterator.php @@ -102,7 +102,7 @@ class SheetIterator implements IteratorInterface * Return the current element * @link http://php.net/manual/en/iterator.current.php * - * @return Sheet + * @return \Box\Spout\Reader\ODS\Sheet */ public function current() { diff --git a/src/Spout/Reader/XLSX/Sheet.php b/src/Spout/Reader/XLSX/Sheet.php index b0babf7..88128e4 100644 --- a/src/Spout/Reader/XLSX/Sheet.php +++ b/src/Spout/Reader/XLSX/Sheet.php @@ -12,7 +12,7 @@ use Box\Spout\Reader\SheetInterface; */ class Sheet implements SheetInterface { - /** @var RowIterator To iterate over sheet's rows */ + /** @var \Box\Spout\Reader\XLSX\RowIterator To iterate over sheet's rows */ protected $rowIterator; /** @var int Index of the sheet, based on order of creation (zero-based) */ @@ -37,7 +37,7 @@ class Sheet implements SheetInterface /** * @api - * @return RowIterator + * @return \Box\Spout\Reader\XLSX\RowIterator */ public function getRowIterator() { diff --git a/src/Spout/Reader/XLSX/SheetIterator.php b/src/Spout/Reader/XLSX/SheetIterator.php index aae58c2..db034f0 100644 --- a/src/Spout/Reader/XLSX/SheetIterator.php +++ b/src/Spout/Reader/XLSX/SheetIterator.php @@ -14,7 +14,7 @@ use Box\Spout\Reader\Exception\NoSheetsFoundException; */ class SheetIterator implements IteratorInterface { - /** @var Sheet[] The list of sheet present in the file */ + /** @var \Box\Spout\Reader\XLSX\Sheet[] The list of sheet present in the file */ protected $sheets; /** @var int The index of the sheet being read (zero-based) */ @@ -79,7 +79,7 @@ class SheetIterator implements IteratorInterface * Return the current element * @link http://php.net/manual/en/iterator.current.php * - * @return Sheet + * @return \Box\Spout\Reader\XLSX\Sheet */ public function current() {