├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── composer.json ├── phpunit.xml.dist ├── spec └── Akeneo │ └── Component │ └── SpreadsheetParser │ ├── Csv │ ├── RowIteratorFactorySpec.php │ ├── RowIteratorSpec.php │ ├── SpreadsheetLoaderSpec.php │ ├── SpreadsheetSpec.php │ └── fixtures │ │ ├── iso-8859-15.csv │ │ ├── test.csv │ │ └── with_options.csv │ ├── SpreadsheetLoaderSpec.php │ └── Xlsx │ ├── ArchiveLoaderSpec.php │ ├── ArchiveSpec.php │ ├── ColumnIndexTransformerSpec.php │ ├── DateTransformerSpec.php │ ├── RelationshipsLoaderSpec.php │ ├── RelationshipsSpec.php │ ├── RowBuilderFactorySpec.php │ ├── RowBuilderSpec.php │ ├── RowIteratorFactorySpec.php │ ├── RowIteratorSpec.php │ ├── SharedStringsLoaderSpec.php │ ├── SharedStringsSpec.php │ ├── SpreadsheetLoaderSpec.php │ ├── SpreadsheetSpec.php │ ├── StylesLoaderSpec.php │ ├── StylesSpec.php │ ├── ValueTransformerFactorySpec.php │ ├── ValueTransformerSpec.php │ ├── WorksheetListReaderSpec.php │ └── fixtures │ ├── sharedStrings.xml │ ├── sheet.xml │ ├── styles.xml │ ├── test.zip │ ├── workbook.xml │ └── workbook.xml.rels ├── src ├── Csv │ ├── CsvParser.php │ ├── RowIterator.php │ ├── RowIteratorFactory.php │ ├── Spreadsheet.php │ └── SpreadsheetLoader.php ├── SpreadsheetInterface.php ├── SpreadsheetLoader.php ├── SpreadsheetLoaderInterface.php ├── SpreadsheetParser.php └── Xlsx │ ├── AbstractXMLDictionnary.php │ ├── AbstractXMLResource.php │ ├── Archive.php │ ├── ArchiveLoader.php │ ├── ColumnIndexTransformer.php │ ├── DateTransformer.php │ ├── Relationships.php │ ├── RelationshipsLoader.php │ ├── RowBuilder.php │ ├── RowBuilderFactory.php │ ├── RowIterator.php │ ├── RowIteratorFactory.php │ ├── SharedStrings.php │ ├── SharedStringsLoader.php │ ├── Spreadsheet.php │ ├── SpreadsheetLoader.php │ ├── Styles.php │ ├── StylesLoader.php │ ├── ValueTransformer.php │ ├── ValueTransformerFactory.php │ ├── WorksheetListReader.php │ └── XlsxParser.php └── tests ├── CsvTest.php ├── XlsxTest.php └── fixtures ├── libreoffice.xlsx ├── msoffice.xlsm ├── msoffice.xlsx ├── test.csv └── test.txt /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | vendor 3 | composer.lock 4 | phpunit.xml 5 | ~$*.xlsx 6 | /nbproject 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | sudo: false 4 | 5 | cache: 6 | directories: 7 | - $HOME/.composer/cache/files 8 | 9 | php: 10 | - 5.4 11 | - 5.5 12 | - 5.6 13 | - 7.0 14 | - 7.1 15 | - 7.2 16 | - 7.3 17 | 18 | env: 19 | - COMPOSER_FLAGS='--prefer-dist' 20 | 21 | matrix: 22 | fast_finish: true 23 | include: 24 | - php: 5.4 25 | env: COMPOSER_FLAGS='--prefer-lowest --prefer-stable' SYMFONY_DEPRECATIONS_HELPER=weak 26 | - php: 5.6 27 | env: COMPOSER_FLAGS='--prefer-dist' DEPENDENCIES=dev 28 | 29 | before_install: 30 | - if [ "$TRAVIS_PHP_VERSION" != "7.3" ]; then phpenv config-rm xdebug.ini; fi 31 | - composer self-update 32 | - if [ "$DEPENDENCIES" = "dev" ]; then composer config minimum-stability dev; fi; 33 | 34 | install: 35 | - composer update $COMPOSER_FLAGS 36 | - bin/simple-phpunit install 37 | 38 | script: 39 | - bin/phpspec run -f dot 40 | - bin/simple-phpunit -v 41 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## 1.2.0 (2016-03-25) 4 | - Use maintained versions of the symfony component 5 | 6 | ## 1.1.7 (2015-10-05) 7 | - Migrate to AkeneoLabs 8 | 9 | ## 1.1.6 (2015-08-12) 10 | - Compatible with Akeneo 1.4 11 | 12 | ## 1.1.5 (2014-09-02) 13 | ### Bug fixes 14 | - set default system encoding to UTF8 15 | 16 | ## 1.1.4 (2014-08-08) 17 | ## Improvements 18 | - Skip converting when reading CSV if source encoding is the same as target encoding 19 | 20 | ## 1.1.3 (2014-06-25) 21 | ### Bug fixes 22 | - Fixed non-static method error in XlsxParser (Github issue #6) 23 | 24 | ## 1.1.2 (2014-06-18) 25 | ### Bug fixes 26 | - first row index of CSV is 1 except of 0 27 | 28 | ## 1.1.1 (2014-06-15) 29 | ### Enhancements 30 | - XLSM extension support 31 | 32 | ## 1.1.0 (2014-06-09) 33 | ### Features 34 | - CSV support 35 | 36 | ### BC Breaks 37 | - WorkbookInterface is now SpreasheetInterface 38 | - WorkbookLoaderInterface is now SpreadsheetLoaderInterface 39 | 40 | ## 1.0.0 (2014-05-24) 41 | - Initial release 42 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Akeneo PIM 2 | 3 | The Open Software License version 3.0 4 | 5 | Copyright (c) 2014, Akeneo SAS. 6 | 7 | Full license is at: http://opensource.org/licenses/OSL-3.0 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Akeneo Spreadsheet Parser 2 | ========================= 3 | 4 | This component is designed to extract data from spreadsheets, while being easy on resources, even for large files. 5 | 6 | The current version of the spreadsheet parser works with csv and xlsx files. 7 | 8 | [![Travis Build Status](https://travis-ci.org/akeneo-labs/spreadsheet-parser.svg?branch=master)](https://travis-ci.org/akeneo-labs/spreadsheet-parser) 9 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/akeneo-labs/spreadsheet-parser/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/akeneo-labs/spreadsheet-parser/?branch=master) 10 | 11 | Installing the package 12 | ---------------------- 13 | 14 | From your application root: 15 | 16 | ```bash 17 | $ php composer.phar require --prefer-dist "akeneo-labs/spreadsheet-parser" 18 | ``` 19 | 20 | Usage 21 | ----- 22 | 23 | To extract data from an XLSX spreadsheet, use the following code: 24 | 25 | ```php 26 | use Akeneo\Component\SpreadsheetParser\SpreadsheetParser; 27 | 28 | $workbook = SpreadsheetParser::open('myfile.xlsx'); 29 | 30 | $myWorksheetIndex = $workbook->getWorksheetIndex('myworksheet'); 31 | 32 | foreach ($workbook->createRowIterator($myWorksheetIndex) as $rowIndex => $values) { 33 | var_dump($rowIndex, $values); 34 | } 35 | ``` 36 | 37 | By using the CSV parser options, you can specify the format of your CSV file : 38 | 39 | ```php 40 | use Akeneo\Component\SpreadsheetParser\SpreadsheetParser; 41 | 42 | $workbook = SpreadsheetParser::open('myfile.csv'); 43 | 44 | $iterator = $workbook->createRowIterator( 45 | 0, 46 | [ 47 | 'encoding' => 'UTF-8', 48 | 'length' => null, 49 | 'delimiter' => ',', 50 | 'enclosure' => '"', 51 | 'escape' => '\\' 52 | ] 53 | ); 54 | 55 | 56 | foreach ($workbook->createRowIterator(0) as $rowIndex => $values) { 57 | var_dump($rowIndex, $values); 58 | } 59 | ``` 60 | 61 | Running the tests 62 | ----------------- 63 | 64 | To run unit tests, use phpspec: 65 | 66 | ```bash 67 | $ php bin/phpspec run 68 | ``` 69 | 70 | To run integration tests, use phpunit: 71 | 72 | ```bash 73 | $ phpunit 74 | ``` 75 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "akeneo-labs/spreadsheet-parser", 3 | "description": "Akeneo Spreadsheet parser. Reads XLXS files from Microsoft Excel and Open Office", 4 | "keywords": ["akeneo", "excel", "xlsx", "reader", "parser", "spreadsheet"], 5 | "homepage": "http://akeneo.com", 6 | "license": "OSL-3.0", 7 | "authors": [ 8 | { 9 | "name": "Antoine Guigan", 10 | "email": "antoine@akeneo.com", 11 | "homepage": "http://akeneo.com" 12 | }, 13 | { 14 | "name": "Matthieu Viel", 15 | "email": "matthieu.viel@akeneo.com", 16 | "homepage": "http://akeneo.com" 17 | }, 18 | { 19 | "name": "JM Leroux", 20 | "email": "jean-marie.leroux@akeneo.com" 21 | } 22 | ], 23 | "require": { 24 | "php": ">=5.4.0", 25 | "symfony/options-resolver": "~2.6 || ~3.0 || ^4.0" 26 | }, 27 | "require-dev": { 28 | "phpspec/phpspec": "~2.0", 29 | "symfony/phpunit-bridge": "^4.2" 30 | }, 31 | "autoload": { 32 | "psr-4": { 33 | "Akeneo\\Component\\SpreadsheetParser\\": "src" 34 | } 35 | }, 36 | "extra": { 37 | "branch-alias": { 38 | "dev-master": "1.2.x-dev" 39 | } 40 | }, 41 | "config": { 42 | "bin-dir": "bin" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | ./tests 18 | 19 | 20 | -------------------------------------------------------------------------------- /spec/Akeneo/Component/SpreadsheetParser/Csv/RowIteratorFactorySpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith('spec\Akeneo\Component\SpreadsheetParser\Csv\StubRowIterator'); 12 | } 13 | 14 | public function it_is_initializable() 15 | { 16 | $this->shouldHaveType('Akeneo\Component\SpreadsheetParser\Csv\RowIteratorFactory'); 17 | } 18 | 19 | public function it_creates_row_iterators() 20 | { 21 | $iterator = $this->create('path', ['options']); 22 | $iterator->getPath()->shouldReturn('path'); 23 | $iterator->getOptions()->shouldReturn(['options']); 24 | } 25 | } 26 | 27 | class StubRowIterator 28 | { 29 | protected $path; 30 | protected $options; 31 | 32 | public function __construct($path, $options) 33 | { 34 | $this->path = $path; 35 | $this->options = $options; 36 | } 37 | 38 | public function getOptions() 39 | { 40 | return $this->options; 41 | } 42 | 43 | public function getPath() 44 | { 45 | return $this->path; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /spec/Akeneo/Component/SpreadsheetParser/Csv/RowIteratorSpec.php: -------------------------------------------------------------------------------- 1 | ['value', 'enclosed value', '15'], 11 | 2 => ['', 'value2', ''], 12 | 3 => ['é', 'è', '€'] 13 | ]; 14 | 15 | public function it_is_initializable() 16 | { 17 | $this->beConstructedWith('path', []); 18 | $this->shouldHaveType('Akeneo\Component\SpreadsheetParser\Csv\RowIterator'); 19 | } 20 | 21 | public function it_parses_csv_files() 22 | { 23 | $this->beConstructedWith(__DIR__ . '/fixtures/test.csv', []); 24 | $this->rewind(); 25 | foreach ($this->values as $i => $row) { 26 | $this->key()->shouldReturn($i); 27 | $this->valid()->shouldReturn(true); 28 | $this->current()->shouldReturn($row); 29 | $this->next(); 30 | } 31 | $this->valid()->shouldReturn(false); 32 | } 33 | 34 | public function it_can_be_rewinded() 35 | { 36 | $this->beConstructedWith(__DIR__ . '/fixtures/test.csv', []); 37 | $this->rewind(); 38 | $this->current()->shouldReturn($this->values[1]); 39 | $this->next(); 40 | $this->rewind(); 41 | $this->current()->shouldReturn($this->values[1]); 42 | } 43 | 44 | public function it_accepts_options() 45 | { 46 | $this->beConstructedWith( 47 | __DIR__ . '/fixtures/with_options.csv', 48 | [ 49 | 'delimiter' => '|', 50 | 'enclosure' => "@" 51 | ] 52 | ); 53 | $this->rewind(); 54 | foreach ($this->values as $i => $row) { 55 | $this->key()->shouldReturn($i); 56 | $this->valid()->shouldReturn(true); 57 | $this->current()->shouldReturn($row); 58 | $this->next(); 59 | } 60 | $this->valid()->shouldReturn(false); 61 | } 62 | 63 | public function it_converts_between_encodings() 64 | { 65 | $this->beConstructedWith( 66 | __DIR__ . '/fixtures/iso-8859-15.csv', 67 | [ 68 | 'encoding' => 'iso-8859-15' 69 | ] 70 | ); 71 | $values = [1 => ['é', 'è', '€']]; 72 | $this->rewind(); 73 | foreach ($values as $i => $row) { 74 | $this->key()->shouldReturn($i); 75 | $this->valid()->shouldReturn(true); 76 | $this->current()->shouldReturn($row); 77 | $this->next(); 78 | } 79 | $this->valid()->shouldReturn(false); 80 | } 81 | 82 | public function it_skips_converting_when_not_necessary() 83 | { 84 | $this->beConstructedWith( 85 | __DIR__ . '/fixtures/with_options.csv', 86 | [ 87 | 'delimiter' => '|', 88 | 'enclosure' => "@", 89 | 'encoding' => 'UTF8' 90 | ] 91 | ); 92 | $this->rewind(); 93 | foreach ($this->values as $i => $row) { 94 | $this->key()->shouldReturn($i); 95 | $this->valid()->shouldReturn(true); 96 | $this->current()->shouldReturn($row); 97 | $this->next(); 98 | } 99 | $this->valid()->shouldReturn(false); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /spec/Akeneo/Component/SpreadsheetParser/Csv/SpreadsheetLoaderSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith( 13 | $rowIteratorFactory, 14 | 'spec\Akeneo\Component\SpreadsheetParser\Csv\StubSpreadsheet', 15 | 'sheet' 16 | ); 17 | } 18 | 19 | public function it_is_initializable() 20 | { 21 | $this->shouldHaveType('Akeneo\Component\SpreadsheetParser\Csv\SpreadsheetLoader'); 22 | } 23 | 24 | public function it_creates_spreadsheet_objects(RowIteratorFactory $rowIteratorFactory) 25 | { 26 | $spreadsheet = $this->open('path'); 27 | $spreadsheet->getPath()->shouldReturn('path'); 28 | $spreadsheet->getSheetName()->shouldReturn('sheet'); 29 | $spreadsheet->getRowIteratorFactory()->shouldReturn($rowIteratorFactory); 30 | } 31 | } 32 | 33 | class StubSpreadsheet 34 | { 35 | protected $rowIteratorFactory; 36 | protected $sheetName; 37 | protected $path; 38 | 39 | public function __construct($rowIteratorFactory, $sheetName, $path) 40 | { 41 | $this->rowIteratorFactory = $rowIteratorFactory; 42 | $this->sheetName = $sheetName; 43 | $this->path = $path; 44 | } 45 | 46 | public function getRowIteratorFactory() 47 | { 48 | return $this->rowIteratorFactory; 49 | } 50 | 51 | public function getSheetName() 52 | { 53 | return $this->sheetName; 54 | } 55 | 56 | public function getPath() 57 | { 58 | return $this->path; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /spec/Akeneo/Component/SpreadsheetParser/Csv/SpreadsheetSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith($rowIteratorFactory, 'sheet', 'path'); 14 | } 15 | 16 | public function it_is_initializable() 17 | { 18 | $this->shouldHaveType('Akeneo\Component\SpreadsheetParser\Csv\Spreadsheet'); 19 | } 20 | 21 | public function it_returns_the_worksheet_list() 22 | { 23 | $this->getWorksheets()->shouldReturn(['sheet']); 24 | } 25 | 26 | public function it_creates_row_iterators( 27 | RowIteratorFactory $rowIteratorFactory, 28 | RowIterator $rowIterator1, 29 | RowIterator $rowIterator2 30 | ) { 31 | $rowIteratorFactory->create('path', ['options1'])->willReturn($rowIterator1); 32 | $rowIteratorFactory->create('path', ['options2'])->willReturn($rowIterator2); 33 | 34 | $this->createRowIterator(0, ['options1'])->shouldReturn($rowIterator1); 35 | $this->createRowIterator(1, ['options2'])->shouldReturn($rowIterator2); 36 | } 37 | 38 | public function it_finds_a_worksheet_index_by_name() 39 | { 40 | $this->getWorksheetIndex('sheet')->shouldReturn(0); 41 | } 42 | 43 | public function it_returns_false_if_a_worksheet_does_not_exist() 44 | { 45 | $this->getWorksheetIndex('sheet3')->shouldReturn(false); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /spec/Akeneo/Component/SpreadsheetParser/Csv/fixtures/iso-8859-15.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akeneo-labs/spreadsheet-parser/4be5fdae57363819dcd80e84ae875c6402213b1b/spec/Akeneo/Component/SpreadsheetParser/Csv/fixtures/iso-8859-15.csv -------------------------------------------------------------------------------- /spec/Akeneo/Component/SpreadsheetParser/Csv/fixtures/test.csv: -------------------------------------------------------------------------------- 1 | value,"enclosed value",15 2 | ,value2, 3 | é,è,€ 4 | -------------------------------------------------------------------------------- /spec/Akeneo/Component/SpreadsheetParser/Csv/fixtures/with_options.csv: -------------------------------------------------------------------------------- 1 | value|@enclosed value@|15 2 | |value2| 3 | é|è|€ -------------------------------------------------------------------------------- /spec/Akeneo/Component/SpreadsheetParser/SpreadsheetLoaderSpec.php: -------------------------------------------------------------------------------- 1 | addLoader('extension', $loader)->addLoader('other_extension', $otherLoader); 18 | $loader->open('file.extension')->willReturn($spreadsheet); 19 | } 20 | 21 | public function it_is_initializable() 22 | { 23 | $this->shouldHaveType('Akeneo\Component\SpreadsheetParser\SpreadsheetLoader'); 24 | } 25 | 26 | public function it_uses_the_loader_corresponding_to_the_file_extension( 27 | SpreadsheetInterface $spreadsheet 28 | ) 29 | { 30 | $this->open('file.extension')->shouldReturn($spreadsheet); 31 | } 32 | 33 | public function it_throws_an_exception_if_no_loader_is_available() 34 | { 35 | $this->shouldThrow('\InvalidArgumentException')->duringOpen('file'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /spec/Akeneo/Component/SpreadsheetParser/Xlsx/ArchiveLoaderSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith('spec\Akeneo\Component\SpreadsheetParser\Xlsx\StubArchive'); 12 | } 13 | 14 | public function it_is_initializable() 15 | { 16 | $this->shouldHaveType('Akeneo\Component\SpreadsheetParser\Xlsx\ArchiveLoader'); 17 | } 18 | 19 | public function it_loads_files() 20 | { 21 | $this->open('path')->getPath()->shouldReturn('path'); 22 | } 23 | } 24 | 25 | class StubArchive 26 | { 27 | protected $path; 28 | 29 | public function __construct($path) 30 | { 31 | $this->path = $path; 32 | } 33 | 34 | public function getPath() 35 | { 36 | return $this->path; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /spec/Akeneo/Component/SpreadsheetParser/Xlsx/ArchiveSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith(__DIR__ . '/fixtures/test.zip'); 12 | } 13 | 14 | public function it_is_initializable() 15 | { 16 | $this->shouldHaveType('Akeneo\Component\SpreadsheetParser\Xlsx\Archive'); 17 | } 18 | 19 | public function it_extracts_files() 20 | { 21 | $this->extract('file1')->shouldHaveFileContent("file1\n"); 22 | } 23 | 24 | public function it_extracts_files_from_subfolders() 25 | { 26 | $this->extract('folder/file2')->shouldHaveFileContent("file2\n"); 27 | } 28 | 29 | public function it_extracts_files_once() 30 | { 31 | $file = $this->extract('file1'); 32 | $file->shouldHaveFileContent("file1\n"); 33 | file_put_contents($file->getWrappedObject(), 'content'); 34 | $this->extract('file1')->shouldHaveFileContent('content'); 35 | } 36 | 37 | public function getMatchers() 38 | { 39 | return [ 40 | 'haveFileContent' => function ($filepath, $content) { 41 | return $content === file_get_contents($filepath); 42 | } 43 | ]; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /spec/Akeneo/Component/SpreadsheetParser/Xlsx/ColumnIndexTransformerSpec.php: -------------------------------------------------------------------------------- 1 | shouldHaveType('Akeneo\Component\SpreadsheetParser\Xlsx\ColumnIndexTransformer'); 12 | } 13 | 14 | public function it_transforms_single_letter_cell_names() 15 | { 16 | $this->transform('A1')->shouldReturn(0); 17 | $this->transform('D360')->shouldReturn(3); 18 | $this->transform('F2')->shouldReturn(5); 19 | } 20 | 21 | public function it_transforms_multiple_letter_cell_names() 22 | { 23 | $this->transform('AF1')->shouldReturn(31); 24 | $this->transform('BC11')->shouldReturn(54); 25 | $this->transform('AAN125')->shouldReturn(715); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /spec/Akeneo/Component/SpreadsheetParser/Xlsx/DateTransformerSpec.php: -------------------------------------------------------------------------------- 1 | shouldHaveType('Akeneo\Component\SpreadsheetParser\Xlsx\DateTransformer'); 12 | } 13 | 14 | public function it_transforms_dates() 15 | { 16 | $this->transform('42001')->shouldReturnDate('2014-12-28 00:00'); 17 | $this->transform('16.9473958333333')->shouldReturnDate('1900-01-15 22:44'); 18 | $this->transform('37027.1041666667')->shouldReturnDate('2001-05-16 02:30'); 19 | } 20 | 21 | public function getMatchers() 22 | { 23 | return [ 24 | 'returnDate' => function ($date, $expected) { 25 | return $expected === $date->format('Y-m-d H:i'); 26 | } 27 | ]; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /spec/Akeneo/Component/SpreadsheetParser/Xlsx/RelationshipsLoaderSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith('spec\Akeneo\Component\SpreadsheetParser\Xlsx\StubRelationships'); 12 | } 13 | 14 | public function it_is_initializable() 15 | { 16 | $this->shouldHaveType('Akeneo\Component\SpreadsheetParser\Xlsx\RelationshipsLoader'); 17 | } 18 | 19 | public function it_loads_relationships() 20 | { 21 | $this->open('path')->getPath()->shouldReturn('path'); 22 | } 23 | } 24 | 25 | class StubRelationships 26 | { 27 | protected $path; 28 | 29 | public function __construct($path) 30 | { 31 | $this->path = $path; 32 | } 33 | 34 | public function getPath() 35 | { 36 | return $this->path; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /spec/Akeneo/Component/SpreadsheetParser/Xlsx/RelationshipsSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith(__DIR__ . '/fixtures/workbook.xml.rels'); 12 | } 13 | 14 | public function it_is_initializable() 15 | { 16 | $this->shouldHaveType('Akeneo\Component\SpreadsheetParser\Xlsx\Relationships'); 17 | } 18 | 19 | public function it_returns_worksheet_paths() 20 | { 21 | $this->getWorksheetPath('rId2')->shouldReturn('xl/worksheets/sheet1.xml'); 22 | $this->getWorksheetPath('rId3')->shouldReturn('xl/worksheets/sheet2.xml'); 23 | $this->getWorksheetPath('rId4')->shouldReturn('xl/worksheets/sheet3.xml'); 24 | } 25 | 26 | public function it_returns_shared_strings_path() 27 | { 28 | $this->getSharedStringsPath()->shouldReturn('xl/sharedStrings.xml'); 29 | } 30 | 31 | public function it_returns_styles_path() 32 | { 33 | $this->getStylesPath()->shouldReturn('xl/styles.xml'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /spec/Akeneo/Component/SpreadsheetParser/Xlsx/RowBuilderFactorySpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith('spec\Akeneo\Component\SpreadsheetParser\Xlsx\StubRowBuilder'); 13 | } 14 | 15 | public function it_is_initializable() 16 | { 17 | $this->shouldHaveType('Akeneo\Component\SpreadsheetParser\Xlsx\RowBuilderFactory'); 18 | } 19 | 20 | public function it_creates_row_builders() 21 | { 22 | $this->create()->shouldHaveType('spec\Akeneo\Component\SpreadsheetParser\Xlsx\StubRowBuilder'); 23 | } 24 | } 25 | 26 | class StubRowBuilder 27 | { 28 | 29 | } 30 | -------------------------------------------------------------------------------- /spec/Akeneo/Component/SpreadsheetParser/Xlsx/RowBuilderSpec.php: -------------------------------------------------------------------------------- 1 | shouldHaveType('Akeneo\Component\SpreadsheetParser\Xlsx\RowBuilder'); 12 | } 13 | 14 | public function it_builds_simple_rows() 15 | { 16 | $this->addValue(0, '0'); 17 | $this->addValue(1, '1'); 18 | $this->addValue(2, '2'); 19 | $this->getData()->shouldReturn(['0', '1', '2']); 20 | } 21 | 22 | public function it_adds_missing_values() 23 | { 24 | $this->addValue(2, '2'); 25 | $this->addValue(6, '6'); 26 | $this->addValue(7, '7'); 27 | $this->getData()->shouldReturn(['', '', '2', '', '', '', '6', '7']); 28 | } 29 | 30 | public function it_right_trims_empty_values() 31 | { 32 | $this->addValue(2, '2'); 33 | $this->addValue(3, ''); 34 | $this->addValue(4, ''); 35 | $this->getData()->shouldReturn(['', '', '2']); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /spec/Akeneo/Component/SpreadsheetParser/Xlsx/RowIteratorFactorySpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith( 16 | $rowBuilderFactory, 17 | $columnIndexTransformer, 18 | 'spec\Akeneo\Component\SpreadsheetParser\Xlsx\StubRowIterator' 19 | ); 20 | } 21 | 22 | public function it_is_initializable() 23 | { 24 | $this->shouldHaveType('Akeneo\Component\SpreadsheetParser\Xlsx\RowIteratorFactory'); 25 | } 26 | 27 | public function it_creates_row_iterators( 28 | RowBuilderFactory $rowBuilderFactory, 29 | ColumnIndexTransformer $columnIndexTransformer, 30 | ValueTransformer $valueTransformer, 31 | Archive $archive 32 | ) { 33 | $iterator = $this->create($valueTransformer, 'path', ['options'], $archive); 34 | $iterator->getPath()->shouldReturn('path'); 35 | $iterator->getOptions()->shouldReturn(['options']); 36 | $iterator->getValueTransformer()->shouldReturn($valueTransformer); 37 | $iterator->getRowBuilderFactory()->shouldReturn($rowBuilderFactory); 38 | $iterator->getColumnIndexTransformer()->shouldReturn($columnIndexTransformer); 39 | } 40 | } 41 | 42 | class StubRowIterator 43 | { 44 | protected $rowBuilderFactory; 45 | protected $columnIndexTransformer; 46 | protected $valueTransformer; 47 | protected $path; 48 | protected $options; 49 | 50 | public function __construct($rowBuilderFactory, $columnIndexTransformer, $valueTransformer, $path, $options) 51 | { 52 | $this->rowBuilderFactory = $rowBuilderFactory; 53 | $this->columnIndexTransformer = $columnIndexTransformer; 54 | $this->valueTransformer = $valueTransformer; 55 | $this->path = $path; 56 | $this->options = $options; 57 | } 58 | 59 | public function getPath() 60 | { 61 | return $this->path; 62 | } 63 | 64 | public function getValueTransformer() 65 | { 66 | return $this->valueTransformer; 67 | } 68 | 69 | public function getRowBuilderFactory() 70 | { 71 | return $this->rowBuilderFactory; 72 | } 73 | 74 | public function getColumnIndexTransformer() 75 | { 76 | return $this->columnIndexTransformer; 77 | } 78 | 79 | public function getOptions() 80 | { 81 | return $this->options; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /spec/Akeneo/Component/SpreadsheetParser/Xlsx/RowIteratorSpec.php: -------------------------------------------------------------------------------- 1 | transform(Argument::that($startWith('A')))->willReturn(0); 29 | $columnIndexTransformer->transform(Argument::that($startWith('B')))->willReturn(1); 30 | $columnIndexTransformer->transform(Argument::that($startWith('C')))->willReturn(2); 31 | $columnIndexTransformer->transform(Argument::that($startWith('D')))->willReturn(3); 32 | 33 | $row = null; 34 | $rowBuilderFactory->create()->will( 35 | function () use ($rowBuilder, &$row) { 36 | $row = []; 37 | 38 | return $rowBuilder; 39 | } 40 | ); 41 | 42 | $rowBuilder->addValue(Argument::type('int'), Argument::type('array'))->will( 43 | function ($args) use (&$row) { 44 | $row[$args[0]] = $args[1]; 45 | } 46 | ); 47 | $rowBuilder->getData()->will( 48 | function () use (&$row) { 49 | return $row; 50 | } 51 | ); 52 | $this->beConstructedWith( 53 | $rowBuilderFactory, 54 | $columnIndexTransformer, 55 | $valueTransformer, 56 | __DIR__ . '/fixtures/sheet.xml', 57 | [], 58 | $archive 59 | ); 60 | $valueTransformer->transform(Argument::type('string'), Argument::type('string'), Argument::type('string')) 61 | ->will( 62 | function ($args) { 63 | return $args; 64 | } 65 | ); 66 | } 67 | 68 | public function it_is_initializable() 69 | { 70 | $this->shouldHaveType('Akeneo\Component\SpreadsheetParser\Xlsx\RowIterator'); 71 | } 72 | 73 | public function it_iterates_through_rows() 74 | { 75 | $values = [ 76 | 1 => [0 => ['0', 's', '0'], 1 => ['1', 's', '0'], 3 => ['', '', '1']], 77 | 2 => [0 => ['2', 's', '0'], 1 => ['3', 's', '0'], 2 => ['4', 's', '0']], 78 | 4 => [0 => ['5', 'n', '0'], 2 => ['6', 'n', '1']], 79 | ]; 80 | 81 | $this->rewind(); 82 | foreach ($values as $key => $row) { 83 | $this->valid()->shouldReturn(true); 84 | $this->current()->shouldReturn($row); 85 | $this->key()->shouldReturn($key); 86 | $this->next(); 87 | } 88 | 89 | $this->valid()->shouldReturn(false); 90 | } 91 | 92 | public function it_can_be_rewinded() 93 | { 94 | $this->rewind(); 95 | $this->valid()->shouldReturn(true); 96 | $this->current()->shouldReturn([0 => ['0', 's', '0'], 1 => ['1', 's', '0'], 3 => ['', '', '1']]); 97 | $this->key()->shouldReturn(1); 98 | $this->next(); 99 | $this->rewind(); 100 | $this->valid()->shouldReturn(true); 101 | $this->current()->shouldReturn([0 => ['0', 's', '0'], 1 => ['1', 's', '0'], 3 => ['', '', '1']]); 102 | $this->key()->shouldReturn(1); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /spec/Akeneo/Component/SpreadsheetParser/Xlsx/SharedStringsLoaderSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith('spec\Akeneo\Component\SpreadsheetParser\Xlsx\StubSharedStrings'); 13 | } 14 | 15 | public function it_is_initializable() 16 | { 17 | $this->shouldHaveType('Akeneo\Component\SpreadsheetParser\Xlsx\SharedStringsLoader'); 18 | } 19 | 20 | public function it_loads_shared_strings(Archive $archive) 21 | { 22 | $sharedStrings = $this->open('path', $archive); 23 | $sharedStrings->getPath()->shouldReturn('path'); 24 | $sharedStrings->getArchive()->shouldReturn($archive); 25 | } 26 | } 27 | 28 | class StubSharedStrings 29 | { 30 | protected $path; 31 | private $archive; 32 | 33 | public function __construct($path, Archive $archive) 34 | { 35 | $this->path = $path; 36 | $this->archive = $archive; 37 | } 38 | 39 | public function getPath() 40 | { 41 | return $this->path; 42 | } 43 | 44 | public function getArchive() 45 | { 46 | return $this->archive; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /spec/Akeneo/Component/SpreadsheetParser/Xlsx/SharedStringsSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith(__DIR__ . '/fixtures/sharedStrings.xml'); 12 | } 13 | 14 | public function it_is_initializable() 15 | { 16 | $this->shouldHaveType('Akeneo\Component\SpreadsheetParser\Xlsx\SharedStrings'); 17 | } 18 | 19 | public function it_returns_shared_strings() 20 | { 21 | $this->get(0)->shouldReturn('value1'); 22 | $this->get(2)->shouldReturn(' '); 23 | $this->get(4)->shouldReturn('value3'); 24 | $this->get(5)->shouldReturn('value4'); 25 | } 26 | 27 | public function it_throws_an_exception_if_there_is_no_string() 28 | { 29 | $this->shouldThrow('\InvalidArgumentException')->duringGet(10); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /spec/Akeneo/Component/SpreadsheetParser/Xlsx/SpreadsheetLoaderSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith( 27 | $archiveLoader, 28 | $relationshipsLoader, 29 | $sharedStringsLoader, 30 | $stylesLoader, 31 | $worksheetListReader, 32 | $valueTransformerFactory, 33 | $rowIteratorFactory, 34 | 'spec\Akeneo\Component\SpreadsheetParser\Xlsx\StubSpreadsheet' 35 | ); 36 | } 37 | 38 | public function it_is_initializable() 39 | { 40 | $this->shouldHaveType('Akeneo\Component\SpreadsheetParser\Xlsx\SpreadsheetLoader'); 41 | } 42 | 43 | public function it_creates_spreadsheet_objects( 44 | RelationshipsLoader $relationshipsLoader, 45 | SharedStringsLoader $sharedStringsLoader, 46 | StylesLoader $stylesLoader, 47 | WorksheetListReader $worksheetListReader, 48 | ValueTransformerFactory $valueTransformerFactory, 49 | RowIteratorFactory $rowIteratorFactory, 50 | ArchiveLoader $archiveLoader, 51 | Archive $archive 52 | ) { 53 | $archiveLoader->open('path')->willReturn($archive); 54 | 55 | $spreadsheet = $this->open('path'); 56 | $spreadsheet->getArchive()->shouldReturn($archive); 57 | $spreadsheet->getSharedStringsLoader()->shouldReturn($sharedStringsLoader); 58 | $spreadsheet->getStylesLoader()->shouldReturn($stylesLoader); 59 | $spreadsheet->getRowIteratorFactory()->shouldReturn($rowIteratorFactory); 60 | $spreadsheet->getWorksheetListReader()->shouldReturn($worksheetListReader); 61 | $spreadsheet->getValueTransformerFactory()->shouldReturn($valueTransformerFactory); 62 | $spreadsheet->getRelationshipsLoader()->shouldReturn($relationshipsLoader); 63 | } 64 | 65 | public function it_caches_spreadsheet_objects( 66 | ArchiveLoader $archiveLoader, 67 | Archive $archive 68 | ) { 69 | $archiveLoader->open('path')->shouldBeCalledTimes(1)->willReturn($archive); 70 | 71 | $spreadsheet = $this->open('path'); 72 | $spreadsheet->getArchive()->shouldReturn($archive); 73 | } 74 | } 75 | 76 | class StubSpreadsheet 77 | { 78 | protected $sharedStringsLoader; 79 | protected $worksheetListReader; 80 | protected $relationshipsLoader; 81 | protected $stylesLoader; 82 | protected $rowIteratorFactory; 83 | protected $valueTransformerFactory; 84 | protected $archive; 85 | 86 | public function __construct( 87 | Archive $archive, 88 | RelationshipsLoader $relationshipsLoader, 89 | SharedStringsLoader $sharedStringsLoader, 90 | StylesLoader $stylesLoader, 91 | WorksheetListReader $worksheetListReader, 92 | ValueTransformerFactory $valueTransformerFactory, 93 | RowIteratorFactory $rowIteratorFactory 94 | ) { 95 | $this->archive = $archive; 96 | $this->sharedStringsLoader = $sharedStringsLoader; 97 | $this->relationshipsLoader = $relationshipsLoader; 98 | $this->stylesLoader = $stylesLoader; 99 | $this->worksheetListReader = $worksheetListReader; 100 | $this->valueTransformerFactory = $valueTransformerFactory; 101 | $this->rowIteratorFactory = $rowIteratorFactory; 102 | } 103 | 104 | public function getSharedStringsLoader() 105 | { 106 | return $this->sharedStringsLoader; 107 | } 108 | 109 | public function getRowIteratorFactory() 110 | { 111 | return $this->rowIteratorFactory; 112 | } 113 | 114 | public function getArchive() 115 | { 116 | return $this->archive; 117 | } 118 | 119 | public function getWorksheetListReader() 120 | { 121 | return $this->worksheetListReader; 122 | } 123 | 124 | public function getRelationshipsLoader() 125 | { 126 | return $this->relationshipsLoader; 127 | } 128 | 129 | public function getValueTransformerFactory() 130 | { 131 | return $this->valueTransformerFactory; 132 | } 133 | 134 | public function getStylesLoader() 135 | { 136 | return $this->stylesLoader; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /spec/Akeneo/Component/SpreadsheetParser/Xlsx/SpreadsheetSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith( 38 | $archive, 39 | $relationshipsLoader, 40 | $sharedStringsLoader, 41 | $stylesLoader, 42 | $worksheetListReader, 43 | $valueTransformerFactory, 44 | $rowIteratorFactory 45 | ); 46 | $archive->extract(Argument::type('string'))->will( 47 | function ($args) { 48 | return sprintf('temp_%s', $args[0]); 49 | } 50 | ); 51 | 52 | $beCalledAtMostOnce = function ($calls, $object, $method) { 53 | if (count($calls) > 1) { 54 | throw new UnexpectedCallsException( 55 | 'Method should be called at most once', 56 | $method, 57 | $calls 58 | ); 59 | } 60 | }; 61 | $relationshipsLoader->open('temp_' . Spreadsheet::RELATIONSHIPS_PATH) 62 | ->should($beCalledAtMostOnce) 63 | ->willReturn($relationships); 64 | 65 | $relationships->getSharedStringsPath()->willReturn('shared_strings'); 66 | $relationships->getStylesPath()->willReturn('styles'); 67 | 68 | $sharedStringsLoader->open('temp_shared_strings', $archive) 69 | ->should($beCalledAtMostOnce) 70 | ->willReturn($sharedStrings); 71 | 72 | $stylesLoader->open(('temp_styles'), $archive)->willReturn($styles); 73 | $valueTransformerFactory->create($sharedStrings, $styles)->willReturn($valueTransformer); 74 | 75 | $worksheetListReader->getWorksheetPaths($relationships, 'temp_' . Spreadsheet::WORKBOOK_PATH) 76 | ->should($beCalledAtMostOnce) 77 | ->willReturn(['sheet1' => 'path1', 'sheet2' => 'path2']); 78 | } 79 | 80 | public function it_is_initializable() 81 | { 82 | $this->shouldHaveType('Akeneo\Component\SpreadsheetParser\Xlsx\Spreadsheet'); 83 | } 84 | 85 | public function it_returns_the_worksheet_list() 86 | { 87 | $this->getWorksheets()->shouldReturn(['sheet1', 'sheet2']); 88 | } 89 | 90 | public function it_creates_row_iterators( 91 | ValueTransformer $valueTransformer, 92 | RowIteratorFactory $rowIteratorFactory, 93 | RowIterator $rowIterator1, 94 | RowIterator $rowIterator2, 95 | Archive $archive 96 | ) { 97 | $rowIteratorFactory->create($valueTransformer, 'temp_path1', [], $archive)->willReturn($rowIterator1); 98 | $rowIteratorFactory->create($valueTransformer, 'temp_path2', [], $archive)->willReturn($rowIterator2); 99 | 100 | $this->createRowIterator(0)->shouldReturn($rowIterator1); 101 | $this->createRowIterator(1)->shouldReturn($rowIterator2); 102 | } 103 | 104 | public function it_finds_a_worksheet_index_by_name() 105 | { 106 | $this->getWorksheetIndex('sheet2')->shouldReturn(1); 107 | } 108 | 109 | public function it_returns_false_if_a_worksheet_does_not_exist() 110 | { 111 | $this->getWorksheetIndex('sheet3')->shouldReturn(false); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /spec/Akeneo/Component/SpreadsheetParser/Xlsx/StylesLoaderSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith('spec\Akeneo\Component\SpreadsheetParser\Xlsx\StubStyles'); 13 | } 14 | 15 | public function it_is_initializable() 16 | { 17 | $this->shouldHaveType('Akeneo\Component\SpreadsheetParser\Xlsx\StylesLoader'); 18 | } 19 | 20 | public function it_loads_styles(Archive $archive) 21 | { 22 | $styles = $this->open('path', $archive); 23 | $styles->getPath()->shouldReturn('path'); 24 | $styles->getArchive()->shouldReturn($archive); 25 | } 26 | } 27 | 28 | class StubStyles 29 | { 30 | protected $path; 31 | private $archive; 32 | 33 | public function __construct($path, Archive $archive) 34 | { 35 | $this->path = $path; 36 | $this->archive = $archive; 37 | } 38 | 39 | public function getPath() 40 | { 41 | return $this->path; 42 | } 43 | 44 | public function getArchive() 45 | { 46 | return $this->archive; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /spec/Akeneo/Component/SpreadsheetParser/Xlsx/StylesSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith(__DIR__ . '/fixtures/styles.xml'); 13 | } 14 | 15 | public function it_is_initializable() 16 | { 17 | $this->shouldHaveType('Akeneo\Component\SpreadsheetParser\Xlsx\Styles'); 18 | } 19 | 20 | public function it_returns_shared_strings() 21 | { 22 | $this->get(0)->shouldReturn(Styles::FORMAT_DEFAULT); 23 | $this->get(1)->shouldReturn(Styles::FORMAT_DATE); 24 | $this->get(2)->shouldReturn(Styles::FORMAT_DEFAULT); 25 | $this->get(3)->shouldReturn(Styles::FORMAT_DATE); 26 | $this->get(4)->shouldReturn(Styles::FORMAT_DATE); 27 | } 28 | 29 | public function it_throws_an_exception_if_there_is_no_style() 30 | { 31 | $this->shouldThrow('\InvalidArgumentException')->duringGet('bogus'); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /spec/Akeneo/Component/SpreadsheetParser/Xlsx/ValueTransformerFactorySpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith($dateTransformer, 'spec\Akeneo\Component\SpreadsheetParser\Xlsx\StubValueTransformer'); 15 | } 16 | 17 | public function it_is_initializable() 18 | { 19 | $this->shouldHaveType('Akeneo\Component\SpreadsheetParser\Xlsx\ValueTransformerFactory'); 20 | } 21 | 22 | public function it_creates_value_transformers( 23 | DateTransformer $dateTransformer, 24 | SharedStrings $sharedStrings, 25 | Styles $styles 26 | ) { 27 | $transformer = $this->create($sharedStrings, $styles); 28 | $transformer->getSharedStrings()->shouldReturn($sharedStrings); 29 | $transformer->getDateTransformer()->shouldReturn($dateTransformer); 30 | $transformer->getStyles()->shouldReturn($styles); 31 | } 32 | } 33 | 34 | class StubValueTransformer 35 | { 36 | protected $dateTransformer; 37 | protected $sharedStrings; 38 | protected $styles; 39 | 40 | public function __construct($dateTransformer, $sharedStrings, $styles) 41 | { 42 | $this->sharedStrings = $sharedStrings; 43 | $this->dateTransformer = $dateTransformer; 44 | $this->styles = $styles; 45 | } 46 | 47 | public function getSharedStrings() 48 | { 49 | return $this->sharedStrings; 50 | } 51 | 52 | public function getDateTransformer() 53 | { 54 | return $this->dateTransformer; 55 | } 56 | 57 | public function getStyles() 58 | { 59 | return $this->styles; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /spec/Akeneo/Component/SpreadsheetParser/Xlsx/ValueTransformerSpec.php: -------------------------------------------------------------------------------- 1 | get('1')->willReturn(Styles::FORMAT_DEFAULT); 20 | $styles->get('2')->willReturn(Styles::FORMAT_DATE); 21 | $dateTransformer->transform(Argument::type('string'))->will( 22 | function ($args) { 23 | return 'date_' . $args[0]; 24 | } 25 | ); 26 | $sharedStrings->get(Argument::type('string'))->will( 27 | function ($args) { 28 | return 'shared_' . $args[0]; 29 | } 30 | ); 31 | $this->beConstructedWith($dateTransformer, $sharedStrings, $styles); 32 | } 33 | 34 | public function it_is_initializable() 35 | { 36 | $this->shouldHaveType('Akeneo\Component\SpreadsheetParser\Xlsx\ValueTransformer'); 37 | } 38 | 39 | public function it_transforms_shared_strings() 40 | { 41 | $this->transform('1', ValueTransformer::TYPE_SHARED_STRING, '1')->shouldReturn('shared_1'); 42 | } 43 | 44 | public function it_transforms_strings() 45 | { 46 | $this->transform('string', ValueTransformer::TYPE_STRING, '1')->shouldReturn('string'); 47 | $this->transform('string', ValueTransformer::TYPE_INLINE_STRING, '1')->shouldReturn('string'); 48 | $this->transform('string', ValueTransformer::TYPE_ERROR, '1')->shouldReturn('string'); 49 | } 50 | 51 | public function it_right_trims_strings() 52 | { 53 | $this->transform(' string ', ValueTransformer::TYPE_STRING, '1')->shouldReturn(' string'); 54 | $this->transform('string ', ValueTransformer::TYPE_SHARED_STRING, '1')->shouldReturn('shared_string'); 55 | $this->transform(' string ', ValueTransformer::TYPE_INLINE_STRING, '1')->shouldReturn(' string'); 56 | $this->transform(' string ', ValueTransformer::TYPE_ERROR, '1')->shouldReturn(' string'); 57 | } 58 | 59 | public function it_transforms_numbers() 60 | { 61 | $this->transform('10.2', ValueTransformer::TYPE_NUMBER, '1')->shouldReturn(10.2); 62 | $this->transform('10.2', '', '1')->shouldReturn(10.2); 63 | } 64 | 65 | public function it_transforms_dates() 66 | { 67 | $this->transform('1', ValueTransformer::TYPE_NUMBER, '2')->shouldReturn('date_1'); 68 | $this->transform('1', '', '2')->shouldReturn('date_1'); 69 | } 70 | 71 | public function it_transforms_boolans() 72 | { 73 | $this->transform('1', ValueTransformer::TYPE_BOOL, null)->shouldReturn(true); 74 | $this->transform('0', ValueTransformer::TYPE_BOOL, '1')->shouldReturn(false); 75 | $this->transform('', ValueTransformer::TYPE_BOOL, '1')->shouldReturn(false); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /spec/Akeneo/Component/SpreadsheetParser/Xlsx/WorksheetListReaderSpec.php: -------------------------------------------------------------------------------- 1 | shouldHaveType('Akeneo\Component\SpreadsheetParser\Xlsx\WorksheetListReader'); 14 | } 15 | 16 | public function it_returns_worksheet_paths(Relationships $relationships) 17 | { 18 | $relationships->getWorksheetPath(\Prophecy\Argument::type('string'))->will( 19 | function ($args) { 20 | return 'file_' . $args[0]; 21 | } 22 | ); 23 | $this->getWorksheetPaths($relationships, __DIR__ . '/fixtures/workbook.xml')->shouldReturn( 24 | [ 25 | 'Worksheet1' => 'file_rId2', 26 | 'Worksheet2' => 'file_rId3', 27 | 'Worksheet3' => 'file_rId4', 28 | ] 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /spec/Akeneo/Component/SpreadsheetParser/Xlsx/fixtures/sharedStrings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | value1 5 | 6 | 7 | value2 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | value3value4 17 | value5 18 | 19 | 20 | values6 21 | 22 | 23 | value7 24 | 25 | 26 | -------------------------------------------------------------------------------- /spec/Akeneo/Component/SpreadsheetParser/Xlsx/fixtures/sheet.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 28 | 39 | 47 | 49 | 50 | 51 | 52 | 53 | 54 | &C&A 55 | &CPage &P 56 | 57 | 58 | -------------------------------------------------------------------------------- /spec/Akeneo/Component/SpreadsheetParser/Xlsx/fixtures/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /spec/Akeneo/Component/SpreadsheetParser/Xlsx/fixtures/test.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akeneo-labs/spreadsheet-parser/4be5fdae57363819dcd80e84ae875c6402213b1b/spec/Akeneo/Component/SpreadsheetParser/Xlsx/fixtures/test.zip -------------------------------------------------------------------------------- /spec/Akeneo/Component/SpreadsheetParser/Xlsx/fixtures/workbook.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /spec/Akeneo/Component/SpreadsheetParser/Xlsx/fixtures/workbook.xml.rels: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/Csv/CsvParser.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class CsvParser 11 | { 12 | /** 13 | * @staticvar string the name of the format 14 | */ 15 | const FORMAT_NAME = 'csv'; 16 | 17 | /** 18 | * @staticvar string Spreadsheet class 19 | */ 20 | const WORKBOOK_CLASS = 'Akeneo\Component\SpreadsheetParser\Csv\Spreadsheet'; 21 | 22 | /** 23 | * @staticvar string RowIterator class 24 | */ 25 | const ROW_ITERATOR_CLASS = 'Akeneo\Component\SpreadsheetParser\Csv\RowIterator'; 26 | 27 | /** 28 | * @staticvar string The name of the sheet 29 | */ 30 | const SHEET_NAME = 'default'; 31 | 32 | /** 33 | * @var SpreadsheetLoader 34 | */ 35 | private static $spreadsheetLoader; 36 | 37 | /** 38 | * Opens a CSV file 39 | * 40 | * @param string $path 41 | * 42 | * @return Spreadsheet 43 | */ 44 | public static function open($path) 45 | { 46 | return static::getSpreadsheetLoader()->open($path); 47 | } 48 | 49 | /** 50 | * @return SpreadsheetLoader 51 | */ 52 | public static function getSpreadsheetLoader() 53 | { 54 | if (!isset(self::$spreadsheetLoader)) { 55 | self::$spreadsheetLoader = new SpreadsheetLoader( 56 | static::createRowIteratorFactory(), 57 | static::WORKBOOK_CLASS, 58 | static::SHEET_NAME 59 | ); 60 | } 61 | 62 | return self::$spreadsheetLoader; 63 | } 64 | 65 | /** 66 | * @return RowIteratorFactory 67 | */ 68 | protected static function createRowIteratorFactory() 69 | { 70 | return new RowIteratorFactory(static::ROW_ITERATOR_CLASS); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Csv/RowIterator.php: -------------------------------------------------------------------------------- 1 | 18 | * @copyright 2014 Akeneo SAS (http://www.akeneo.com) 19 | * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) 20 | */ 21 | class RowIterator implements \Iterator 22 | { 23 | /** 24 | * @var string 25 | */ 26 | protected $path; 27 | 28 | /** 29 | * @var array 30 | */ 31 | protected $options; 32 | 33 | /** 34 | * @var resource 35 | */ 36 | protected $fileHandle; 37 | 38 | /** 39 | * @var int 40 | */ 41 | protected $currentKey; 42 | 43 | /** 44 | * @var array 45 | */ 46 | protected $currentValue; 47 | 48 | /** 49 | * @var boolean 50 | */ 51 | protected $valid; 52 | 53 | /** 54 | * Constructor 55 | * 56 | * @param string $path 57 | * @param array $options 58 | */ 59 | public function __construct( 60 | $path, 61 | array $options 62 | ) { 63 | $this->path = $path; 64 | $resolver = new OptionsResolver; 65 | $this->setDefaultOptions($resolver); 66 | $this->options = $resolver->resolve($options); 67 | } 68 | 69 | /** 70 | * {@inheritdoc} 71 | */ 72 | public function current() 73 | { 74 | return $this->currentValue; 75 | } 76 | 77 | /** 78 | * {@inheritdoc} 79 | */ 80 | public function key() 81 | { 82 | return $this->currentKey; 83 | } 84 | 85 | /** 86 | * {@inheritdoc} 87 | */ 88 | public function next() 89 | { 90 | $this->currentValue = fgetcsv( 91 | $this->fileHandle, 92 | $this->options['length'], 93 | $this->options['delimiter'], 94 | $this->options['enclosure'], 95 | $this->options['escape'] 96 | ); 97 | $this->currentKey++; 98 | $this->valid = (false !== $this->currentValue); 99 | } 100 | 101 | /** 102 | * {@inheritdoc} 103 | */ 104 | public function rewind() 105 | { 106 | if ($this->fileHandle) { 107 | rewind($this->fileHandle); 108 | } else { 109 | $this->openResource(); 110 | } 111 | $this->currentKey = 0; 112 | $this->next(); 113 | } 114 | 115 | /** 116 | * {@inheritdoc} 117 | */ 118 | public function valid() 119 | { 120 | return $this->valid; 121 | } 122 | 123 | /** 124 | * Sets the default options 125 | * 126 | * @param OptionsResolver $resolver 127 | */ 128 | protected function setDefaultOptions(OptionsResolver $resolver) 129 | { 130 | $resolver->setDefined(['encoding']); 131 | $resolver->setDefaults( 132 | [ 133 | 'length' => null, 134 | 'delimiter' => ',', 135 | 'enclosure' => '"', 136 | 'escape' => '\\' 137 | ] 138 | ); 139 | } 140 | 141 | /** 142 | * Opens the file resource 143 | */ 144 | protected function openResource() 145 | { 146 | $this->fileHandle = fopen($this->path, 'r'); 147 | $currentEncoding = $this->getCurrentEncoding(); 148 | 149 | if (isset($this->options['encoding']) && $currentEncoding !== $this->options['encoding']) { 150 | stream_filter_prepend( 151 | $this->fileHandle, 152 | sprintf( 153 | "convert.iconv.%s/%s", 154 | $this->options['encoding'], 155 | $this->getCurrentEncoding() 156 | ) 157 | ); 158 | } 159 | } 160 | 161 | /** 162 | * Returns the server encoding 163 | * 164 | * @return string 165 | */ 166 | protected function getCurrentEncoding() 167 | { 168 | $locale = explode('.', setlocale(LC_CTYPE, 0)); 169 | 170 | return isset($locale[1]) ? $locale[1] : 'UTF8'; 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/Csv/RowIteratorFactory.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2014 Akeneo SAS (http://www.akeneo.com) 10 | * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) 11 | */ 12 | class RowIteratorFactory 13 | { 14 | /** 15 | * @var string 16 | */ 17 | protected $iteratorClass; 18 | 19 | /** 20 | * Constructor 21 | * 22 | * @param string $iteratorClass the class for row iterators 23 | */ 24 | public function __construct($iteratorClass) 25 | { 26 | $this->iteratorClass = $iteratorClass; 27 | } 28 | 29 | /** 30 | * Creates a row iterator for the XML given worksheet file 31 | * 32 | * @param string $path the path to the extracted XML worksheet file 33 | * @param array $options options specific to the format 34 | * 35 | * @return RowIterator 36 | */ 37 | public function create($path, array $options) 38 | { 39 | return new $this->iteratorClass($path, $options); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/Csv/Spreadsheet.php: -------------------------------------------------------------------------------- 1 | 11 | * @copyright 2014 Akeneo SAS (http://www.akeneo.com) 12 | * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) 13 | */ 14 | class Spreadsheet implements SpreadsheetInterface 15 | { 16 | /** 17 | * @var RowIteratorFactory 18 | */ 19 | protected $rowIteratorFactory; 20 | 21 | /** 22 | * @var string 23 | */ 24 | protected $sheetName; 25 | 26 | /** 27 | * @var string 28 | */ 29 | protected $path; 30 | 31 | /** 32 | * Constructor 33 | * 34 | * @param RowIteratorFactory $rowIteratorFactory 35 | * @param string $sheetName 36 | * @param string $path 37 | */ 38 | public function __construct(RowIteratorFactory $rowIteratorFactory, $sheetName, $path) 39 | { 40 | $this->rowIteratorFactory = $rowIteratorFactory; 41 | $this->sheetName = $sheetName; 42 | $this->path = $path; 43 | } 44 | 45 | /** 46 | * {@inheritdoc} 47 | */ 48 | public function getWorksheets() 49 | { 50 | return [$this->sheetName]; 51 | } 52 | 53 | /** 54 | * {@inheritdoc} 55 | */ 56 | public function createRowIterator($worksheetIndex, array $options = []) 57 | { 58 | return $this->rowIteratorFactory->create($this->path, $options); 59 | } 60 | 61 | /** 62 | * {@inheritdoc} 63 | */ 64 | public function getWorksheetIndex($name) 65 | { 66 | return $this->sheetName === $name ? 0 : false; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Csv/SpreadsheetLoader.php: -------------------------------------------------------------------------------- 1 | 11 | * @copyright 2014 Akeneo SAS (http://www.akeneo.com) 12 | * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) 13 | */ 14 | class SpreadsheetLoader implements SpreadsheetLoaderInterface 15 | { 16 | /** 17 | * @var RowIteratorFactory 18 | */ 19 | protected $rowIteratorFactory; 20 | 21 | /** 22 | * @var string 23 | */ 24 | protected $spreadsheetClass; 25 | 26 | /** 27 | * 28 | * @var string 29 | */ 30 | protected $sheetName; 31 | 32 | /** 33 | * Constructor 34 | * 35 | * @param RowIteratorFactory $rowIteratorFactory 36 | * @param string $spreadsheetClass 37 | * @param string $sheetName 38 | */ 39 | public function __construct(RowIteratorFactory $rowIteratorFactory, $spreadsheetClass, $sheetName) 40 | { 41 | $this->rowIteratorFactory = $rowIteratorFactory; 42 | $this->spreadsheetClass = $spreadsheetClass; 43 | $this->sheetName = $sheetName; 44 | } 45 | 46 | /** 47 | * {@inheritdoc} 48 | */ 49 | public function open($path, $type = null) 50 | { 51 | return new $this->spreadsheetClass( 52 | $this->rowIteratorFactory, 53 | $this->sheetName, 54 | $path 55 | ); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/SpreadsheetInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2014 Akeneo SAS (http://www.akeneo.com) 10 | * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) 11 | */ 12 | interface SpreadsheetInterface 13 | { 14 | /** 15 | * Returns an array containing all worksheet names 16 | * 17 | * The keys of the array should be the indexes of the worksheets 18 | * 19 | * @return string[] 20 | */ 21 | public function getWorksheets(); 22 | 23 | /** 24 | * Returns a row iterator for the current worksheet index 25 | * 26 | * @param int $worksheetIndex 27 | * @param array $options 28 | * 29 | * @return \Iterator 30 | */ 31 | public function createRowIterator($worksheetIndex, array $options = []); 32 | 33 | /** 34 | * Returns a worksheet index by name 35 | * 36 | * @param string $name 37 | * 38 | * @return int|false Returns false in case there is no worksheet with this name 39 | */ 40 | public function getWorksheetIndex($name); 41 | } 42 | -------------------------------------------------------------------------------- /src/SpreadsheetLoader.php: -------------------------------------------------------------------------------- 1 | 11 | * @copyright 2014 Akeneo SAS (http://www.akeneo.com) 12 | * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) 13 | */ 14 | class SpreadsheetLoader implements SpreadsheetLoaderInterface 15 | { 16 | /** 17 | * @var SpreadsheetLoaderInterface[] 18 | */ 19 | protected $loaders = []; 20 | 21 | /** 22 | * Opens a spreadsheet 23 | * 24 | * @param string $path 25 | * @param string|null $type 26 | * 27 | * @return SpreadsheetInterface 28 | * 29 | * @throws InvalidArgumentException 30 | */ 31 | public function open($path, $type = null) 32 | { 33 | $type = $type ?: $this->getType($path); 34 | if (!isset($this->loaders[$type])) { 35 | throw new InvalidArgumentException(sprintf('No loader for type %s', $type)); 36 | } 37 | 38 | return $this->loaders[$type]->open($path); 39 | } 40 | 41 | /** 42 | * Addds a loader for a specified type 43 | * 44 | * @param string $type 45 | * @param SpreadsheetLoaderInterface $loader 46 | * 47 | * @return SpreadsheetLoader 48 | */ 49 | public function addLoader($type, SpreadsheetLoaderInterface $loader) 50 | { 51 | $this->loaders[$type] = $loader; 52 | 53 | return $this; 54 | } 55 | 56 | /** 57 | * Returns the type for a path 58 | * 59 | * @param string $path 60 | * 61 | * @return string 62 | */ 63 | protected function getType($path) 64 | { 65 | return strtolower(pathinfo($path, PATHINFO_EXTENSION)); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/SpreadsheetLoaderInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2014 Akeneo SAS (http://www.akeneo.com) 10 | * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) 11 | */ 12 | interface SpreadsheetLoaderInterface 13 | { 14 | /** 15 | * Opens a spreadsheet and returns a Spreadsheet object 16 | * 17 | * Spreadsheet objects are cached, and will be read only once 18 | * 19 | * @param string $path 20 | * @param string|null $type 21 | * 22 | * @return SpreadsheetInterface 23 | */ 24 | public function open($path, $type = null); 25 | } 26 | -------------------------------------------------------------------------------- /src/SpreadsheetParser.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2014 Akeneo SAS (http://www.akeneo.com) 10 | * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) 11 | */ 12 | class SpreadsheetParser 13 | { 14 | /** 15 | * @var SpreadsheetLoader 16 | */ 17 | protected static $spreadsheetLoader; 18 | 19 | /** 20 | * Opens a spreadsheet 21 | * 22 | * @param string $path 23 | * @param string|null $type 24 | * 25 | * @return SpreadsheetInterface 26 | */ 27 | public static function open($path, $type = null) 28 | { 29 | return static::getSpreadsheetLoader()->open($path, $type); 30 | } 31 | 32 | /** 33 | * Returns the spreadsheet loader 34 | * 35 | * @return SpreadsheetLoaderInterface 36 | */ 37 | public static function getSpreadsheetLoader() 38 | { 39 | if (!isset(static::$spreadsheetLoader)) { 40 | static::$spreadsheetLoader = new SpreadsheetLoader(); 41 | static::configureLoaders(); 42 | } 43 | 44 | return static::$spreadsheetLoader; 45 | } 46 | 47 | /** 48 | * Configure the loaders 49 | */ 50 | protected static function configureLoaders() 51 | { 52 | static::$spreadsheetLoader 53 | ->addLoader(Xlsx\XlsxParser::FORMAT_NAME, Xlsx\XlsxParser::getSpreadsheetLoader()) 54 | ->addLoader(Xlsx\XlsxParser::MACRO_FORMAT_NAME, Xlsx\XlsxParser::getSpreadsheetLoader()) 55 | ->addLoader(Csv\CsvParser::FORMAT_NAME, Csv\CsvParser::getSpreadsheetLoader()); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Xlsx/AbstractXMLDictionnary.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2014 Akeneo SAS (http://www.akeneo.com) 10 | * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) 11 | */ 12 | abstract class AbstractXMLDictionnary extends AbstractXMLResource 13 | { 14 | /** 15 | * @var boolean 16 | */ 17 | protected $valid = true; 18 | 19 | /** 20 | * @var array 21 | */ 22 | protected $values = []; 23 | 24 | /** 25 | * Returns a shared string by index 26 | * 27 | * @param int $index 28 | * 29 | * @throws \InvalidArgumentException 30 | */ 31 | public function get($index) 32 | { 33 | while ($this->valid && !isset($this->values[$index])) { 34 | $this->readNext(); 35 | } 36 | if ((!isset($this->values[$index]))) { 37 | throw new \InvalidArgumentException(sprintf('No value with index %s', $index)); 38 | } 39 | 40 | return $this->values[$index]; 41 | } 42 | 43 | /** 44 | * Reads the next value in the file 45 | */ 46 | abstract protected function readNext(); 47 | } 48 | -------------------------------------------------------------------------------- /src/Xlsx/AbstractXMLResource.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2014 Akeneo SAS (http://www.akeneo.com) 10 | * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) 11 | */ 12 | abstract class AbstractXMLResource 13 | { 14 | /** 15 | * @var string 16 | */ 17 | protected $path; 18 | 19 | /** 20 | * @var \XMLReader 21 | */ 22 | private $xml; 23 | 24 | /** 25 | * The Archive from which the path was extracted. 26 | * 27 | * A reference to the object is kept here to ensure that it is not deleted 28 | * before the RowIterator, as this would remove the extraction folder. 29 | * 30 | * @var Archive 31 | */ 32 | private $archive; 33 | 34 | /** 35 | * Constructor 36 | * 37 | * @param string $path path to the extracted shared strings XML file 38 | * @param Archive|null $archive The Archive from which the path was extracted 39 | */ 40 | public function __construct($path, Archive $archive = null) 41 | { 42 | $this->path = $path; 43 | $this->archive = $archive; 44 | } 45 | 46 | /** 47 | * @inheritdoc 48 | */ 49 | public function __destruct() 50 | { 51 | if ($this->xml) { 52 | $this->closeXMLReader(); 53 | } 54 | } 55 | 56 | /** 57 | * Returns the XML reader 58 | * 59 | * @return \XMLReader 60 | */ 61 | protected function getXMLReader() 62 | { 63 | if (!$this->xml) { 64 | $this->xml = $this->createXMLReader(); 65 | } 66 | 67 | return $this->xml; 68 | } 69 | 70 | /** 71 | * Creates the XML Reader 72 | * 73 | * @return \XMLReader 74 | */ 75 | protected function createXMLReader() 76 | { 77 | $xml = new \XMLReader(); 78 | $xml->open($this->path); 79 | 80 | return $xml; 81 | } 82 | 83 | /** 84 | * Closes the XML reader 85 | */ 86 | protected function closeXMLReader() 87 | { 88 | $this->xml->close(); 89 | $this->xml = null; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Xlsx/Archive.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2014 Akeneo SAS (http://www.akeneo.com) 10 | * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) 11 | */ 12 | class Archive 13 | { 14 | /** 15 | * 16 | * @var string 17 | */ 18 | protected $tempPath; 19 | 20 | /** 21 | * 22 | * @var string 23 | */ 24 | protected $archivePath; 25 | 26 | /** 27 | * @var \ZipArchive 28 | */ 29 | private $zip; 30 | 31 | /** 32 | * Constructor 33 | * 34 | * @param string $archivePath 35 | */ 36 | public function __construct($archivePath) 37 | { 38 | $this->archivePath = $archivePath; 39 | $this->tempPath = tempnam(sys_get_temp_dir(), 'xls_parser_archive'); 40 | unlink($this->tempPath); 41 | } 42 | 43 | /** 44 | * Extracts the specified file to a temp path, and return the temp path 45 | * 46 | * Files are only extracted once for the given archive 47 | * 48 | * @param string $filePath 49 | * 50 | * @return string 51 | */ 52 | public function extract($filePath) 53 | { 54 | $tempPath = sprintf('%s/%s', $this->tempPath, $filePath); 55 | if (!file_exists($tempPath)) { 56 | $this->getArchive()->extractTo($this->tempPath, $filePath); 57 | } 58 | 59 | return $tempPath; 60 | } 61 | 62 | /** 63 | * Clears all extracted files 64 | */ 65 | public function __destruct() 66 | { 67 | $this->deleteTemp(); 68 | $this->closeArchive(); 69 | } 70 | 71 | /** 72 | * Returns the archive 73 | * 74 | * @return \ZipArchive 75 | */ 76 | protected function getArchive() 77 | { 78 | if (!$this->zip) { 79 | $this->zip = new \ZipArchive(); 80 | if (true !== $errorCode = $this->zip->open($this->archivePath)) { 81 | $this->zip = null; 82 | throw new \RuntimeException('Error opening file: '.$this->getErrorMessage($errorCode)); 83 | } 84 | } 85 | 86 | return $this->zip; 87 | } 88 | 89 | /** 90 | * Closes the archive 91 | */ 92 | protected function closeArchive() 93 | { 94 | if ($this->zip) { 95 | $this->zip->close(); 96 | $this->zip = null; 97 | } 98 | } 99 | 100 | /** 101 | * Deletes temporary files 102 | * 103 | * @return null 104 | */ 105 | protected function deleteTemp() 106 | { 107 | if (!file_exists($this->tempPath)) { 108 | return; 109 | } 110 | 111 | $files = new \RecursiveIteratorIterator( 112 | new \RecursiveDirectoryIterator($this->tempPath, \RecursiveDirectoryIterator::SKIP_DOTS), 113 | \RecursiveIteratorIterator::CHILD_FIRST 114 | ); 115 | foreach ($files as $file) { 116 | if ($file->isDir()) { 117 | rmdir($file->getRealPath()); 118 | } else { 119 | unlink($file->getRealPath()); 120 | } 121 | } 122 | rmdir($this->tempPath); 123 | } 124 | 125 | /** 126 | * Gets an error message from the error code 127 | * 128 | * @param string $errorCode 129 | * 130 | * @return string 131 | */ 132 | protected function getErrorMessage($errorCode) 133 | { 134 | switch ($errorCode) { 135 | case \ZipArchive::ER_MULTIDISK: 136 | return 'Multi-disk zip archives not supported'; 137 | 138 | case \ZipArchive::ER_RENAME: 139 | return 'Renaming temporary file failed'; 140 | 141 | case \ZipArchive::ER_CLOSE: 142 | return 'Closing zip archive failed'; 143 | 144 | case \ZipArchive::ER_SEEK: 145 | return 'Seek error'; 146 | 147 | case \ZipArchive::ER_READ: 148 | return 'Read error'; 149 | 150 | case \ZipArchive::ER_WRITE: 151 | return 'Write error'; 152 | 153 | case \ZipArchive::ER_CRC: 154 | return 'CRC error'; 155 | 156 | case \ZipArchive::ER_ZIPCLOSED: 157 | return 'Containing zip archive was closed'; 158 | 159 | case \ZipArchive::ER_NOENT: 160 | return 'No such file'; 161 | 162 | case \ZipArchive::ER_EXISTS: 163 | return 'File already exists'; 164 | 165 | case \ZipArchive::ER_OPEN: 166 | return 'Can\'t open file'; 167 | 168 | case \ZipArchive::ER_TMPOPEN: 169 | return 'Failure to create temporary file'; 170 | 171 | case \ZipArchive::ER_ZLIB: 172 | return 'Zlib error'; 173 | 174 | case \ZipArchive::ER_MEMORY: 175 | return 'Malloc failure'; 176 | 177 | case \ZipArchive::ER_CHANGED: 178 | return 'Entry has been changed'; 179 | 180 | case \ZipArchive::ER_COMPNOTSUPP: 181 | return 'Compression method not supported'; 182 | 183 | case \ZipArchive::ER_EOF: 184 | return 'Premature EOF'; 185 | 186 | case \ZipArchive::ER_INVAL: 187 | return 'Invalid argument'; 188 | 189 | case \ZipArchive::ER_NOZIP: 190 | return 'Not a zip archive'; 191 | 192 | case \ZipArchive::ER_INTERNAL: 193 | return 'Internal error'; 194 | 195 | case \ZipArchive::ER_INCONS: 196 | return 'Zip archive inconsistent'; 197 | 198 | case \ZipArchive::ER_REMOVE: 199 | return 'Can\'t remove file'; 200 | 201 | case \ZipArchive::ER_DELETED: 202 | return 'Entry has been deleted'; 203 | 204 | default: 205 | return 'An unknown error has occurred('.intval($errorCode).')'; 206 | } 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/Xlsx/ArchiveLoader.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2014 Akeneo SAS (http://www.akeneo.com) 10 | * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) 11 | */ 12 | class ArchiveLoader 13 | { 14 | /** 15 | * 16 | * @var string 17 | */ 18 | protected $archiveClass; 19 | 20 | /** 21 | * Constructor 22 | * 23 | * @param string $archiveClass The class of loaded objects 24 | */ 25 | public function __construct($archiveClass) 26 | { 27 | $this->archiveClass = $archiveClass; 28 | } 29 | 30 | /** 31 | * Opens the given archive 32 | * 33 | * @param string $path 34 | * 35 | * @return Archive 36 | */ 37 | public function open($path) 38 | { 39 | return new $this->archiveClass($path); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Xlsx/ColumnIndexTransformer.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2014 Akeneo SAS (http://www.akeneo.com) 10 | * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) 11 | */ 12 | 13 | class ColumnIndexTransformer 14 | { 15 | /** 16 | * Transforms an Excel cell name in an index 17 | * 18 | * @param string $name 19 | * 20 | * @return integer 21 | */ 22 | public function transform($name) 23 | { 24 | $number = -1; 25 | 26 | foreach (str_split($name) as $chr) { 27 | $digit = ord($chr) - 65; 28 | if ($digit < 0) { 29 | break; 30 | } 31 | $number = ($number + 1) * 26 + $digit; 32 | } 33 | 34 | return $number; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Xlsx/DateTransformer.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2014 Akeneo SAS (http://www.akeneo.com) 10 | * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) 11 | */ 12 | class DateTransformer 13 | { 14 | /** 15 | * @var \DateTime 16 | */ 17 | protected $baseDate; 18 | 19 | /** 20 | * Constructor 21 | */ 22 | public function __construct() 23 | { 24 | $this->baseDate = new \DateTime('1900-01-00 00:00:00 UTC'); 25 | } 26 | 27 | /** 28 | * Transforms an Excel date into a DateTime object 29 | * 30 | * @param String $value 31 | * 32 | * @return \DateTime 33 | */ 34 | public function transform($value) 35 | { 36 | $days = floor($value); 37 | 38 | $seconds = round(($value - $days) * 86400); 39 | 40 | $date = clone $this->baseDate; 41 | $date->modify(sprintf('+%sday +%ssecond', $days - 1, $seconds)); 42 | 43 | return $date; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Xlsx/Relationships.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2014 Akeneo SAS (http://www.akeneo.com) 10 | * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) 11 | */ 12 | class Relationships extends AbstractXMLResource 13 | { 14 | 15 | /** 16 | * 17 | * @var string 18 | */ 19 | protected $relationshipsPath; 20 | 21 | /** 22 | * 23 | * @var array 24 | */ 25 | protected $workSheetPaths; 26 | 27 | /** 28 | * 29 | * @var string 30 | */ 31 | protected $stylePath; 32 | 33 | /** 34 | * 35 | * @var string 36 | */ 37 | protected $sharedStringPath; 38 | 39 | /** 40 | * Constructor 41 | * 42 | * @param string $path the path to the XML relationships file 43 | */ 44 | public function __construct($path) 45 | { 46 | parent::__construct($path); 47 | $xml = $this->getXMLReader(); 48 | 49 | while ($xml->read()) { 50 | if (\XMLReader::ELEMENT === $xml->nodeType && 'Relationship' === $xml->name) { 51 | 52 | $type = basename((string)$xml->getAttribute('Type')); 53 | $this->storeRelationShipByType($type, $xml->getAttribute('Id'), 'xl/' . $xml->getAttribute('Target')); 54 | } 55 | } 56 | 57 | $this->closeXMLReader(); 58 | } 59 | 60 | /** 61 | * Returns the path of a worksheet file inside the xlsx file 62 | * 63 | * @param string $id 64 | * 65 | * @return string 66 | */ 67 | public function getWorksheetPath($id) 68 | { 69 | return $this->workSheetPaths[$id]; 70 | } 71 | 72 | /** 73 | * Returns the path of the shared strings file inside the xlsx file 74 | * 75 | * @return string 76 | */ 77 | public function getSharedStringsPath() 78 | { 79 | return $this->sharedStringPath; 80 | } 81 | 82 | /** 83 | * Returns the path of the styles XML file inside the xlsx file 84 | * 85 | * @return string 86 | */ 87 | public function getStylesPath() 88 | { 89 | return $this->stylePath; 90 | } 91 | 92 | /** 93 | * stores the relationShip into the right variable 94 | * 95 | * @param string $type 96 | * @param string $id 97 | * @param string $target 98 | */ 99 | private function storeRelationShipByType($type, $id, $target) 100 | { 101 | switch ($type) { 102 | case 'worksheet': 103 | $this->workSheetPaths[$id] = $target; 104 | break; 105 | case 'styles': 106 | $this->stylePath = $target; 107 | break; 108 | case 'sharedStrings': 109 | $this->sharedStringPath = $target; 110 | break; 111 | } 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /src/Xlsx/RelationshipsLoader.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2014 Akeneo SAS (http://www.akeneo.com) 10 | * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) 11 | */ 12 | class RelationshipsLoader 13 | { 14 | /** 15 | * 16 | * @var string 17 | */ 18 | protected $relationshipClass; 19 | 20 | /** 21 | * Constructor 22 | * 23 | * @param string $relationshipClass The class of the relationship objects 24 | */ 25 | public function __construct($relationshipClass) 26 | { 27 | $this->relationshipClass = $relationshipClass; 28 | } 29 | 30 | /** 31 | * Opens a relationships file 32 | * 33 | * @param string $path 34 | * 35 | * @return Relationships 36 | */ 37 | public function open($path) 38 | { 39 | return new $this->relationshipClass($path); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Xlsx/RowBuilder.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2014 Akeneo SAS (http://www.akeneo.com) 10 | * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) 11 | */ 12 | class RowBuilder 13 | { 14 | 15 | /** 16 | * 17 | * @var array 18 | */ 19 | protected $values = []; 20 | 21 | /** 22 | * Adds a value to the row 23 | * 24 | * @param int $columnIndex 25 | * @param string $value 26 | */ 27 | public function addValue($columnIndex, $value) 28 | { 29 | if ('' !== $value) { 30 | $this->values[$columnIndex] = $value; 31 | } 32 | } 33 | 34 | /** 35 | * Returns the read row 36 | * 37 | * @return array 38 | */ 39 | public function getData() 40 | { 41 | $data = []; 42 | foreach ($this->values as $columnIndex => $value) { 43 | while (count($data) < $columnIndex) { 44 | $data[] = ''; 45 | } 46 | $data[] = $value; 47 | } 48 | 49 | return $data; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Xlsx/RowBuilderFactory.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2014 Akeneo SAS (http://www.akeneo.com) 10 | * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) 11 | */ 12 | 13 | class RowBuilderFactory 14 | { 15 | 16 | /** 17 | * 18 | * @var string 19 | */ 20 | protected $rowBuilderClass; 21 | 22 | /** 23 | * Constructor 24 | * 25 | * @param string $rowBuilderClass 26 | */ 27 | public function __construct($rowBuilderClass) 28 | { 29 | $this->rowBuilderClass = $rowBuilderClass; 30 | } 31 | 32 | /** 33 | * Creates a RowBuilder object 34 | * 35 | * @return RowBuilder 36 | */ 37 | public function create() 38 | { 39 | return new $this->rowBuilderClass(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Xlsx/RowIterator.php: -------------------------------------------------------------------------------- 1 | 13 | * @copyright 2014 Akeneo SAS (http://www.akeneo.com) 14 | * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) 15 | */ 16 | class RowIterator implements \Iterator 17 | { 18 | /** 19 | * @var RowBuilderFactory 20 | */ 21 | protected $rowBuilderFactory; 22 | 23 | /** 24 | * @var ColumnIndexTransformer 25 | */ 26 | protected $columnIndexTransformer; 27 | 28 | /** 29 | * @var ValueTransformer 30 | */ 31 | protected $valueTransformer; 32 | 33 | /** 34 | * @var string 35 | */ 36 | protected $path; 37 | 38 | /** 39 | * @var array 40 | */ 41 | protected $options; 42 | 43 | /** 44 | * @var \XMLReader 45 | */ 46 | protected $xml; 47 | 48 | /** 49 | * @var int 50 | */ 51 | protected $currentKey; 52 | 53 | /** 54 | * @var array 55 | */ 56 | protected $currentValue; 57 | 58 | /** 59 | * @var boolean 60 | */ 61 | protected $valid; 62 | 63 | /** 64 | * The Archive from which the path was extracted. 65 | * 66 | * A reference to the object is kept here to ensure that it is not deleted 67 | * before the RowIterator, as this would remove the extraction folder. 68 | * 69 | * @var Archive 70 | */ 71 | private $archive; 72 | 73 | /** 74 | * Constructor 75 | * 76 | * @param RowBuilderFactory $rowBuilderFactory 77 | * @param ColumnIndexTransformer $columnIndexTransformer 78 | * @param ValueTransformer $valueTransformer 79 | * @param string $path 80 | * @param array $options 81 | * @param Archive $archive The Archive from which the path was extracted 82 | */ 83 | public function __construct( 84 | RowBuilderFactory $rowBuilderFactory, 85 | ColumnIndexTransformer $columnIndexTransformer, 86 | ValueTransformer $valueTransformer, 87 | $path, 88 | array $options, 89 | Archive $archive 90 | ) { 91 | $this->rowBuilderFactory = $rowBuilderFactory; 92 | $this->columnIndexTransformer = $columnIndexTransformer; 93 | $this->valueTransformer = $valueTransformer; 94 | $this->path = $path; 95 | $this->options = $options; 96 | $this->archive = $archive; 97 | } 98 | 99 | /** 100 | * {@inheritdoc} 101 | */ 102 | public function current() 103 | { 104 | return $this->currentValue; 105 | } 106 | 107 | /** 108 | * {@inheritdoc} 109 | */ 110 | public function key() 111 | { 112 | return $this->currentKey; 113 | } 114 | 115 | /** 116 | * {@inheritdoc} 117 | */ 118 | public function next() 119 | { 120 | $this->valid = false; 121 | 122 | $style = null; 123 | $type = null; 124 | $columnIndex = null; 125 | $rowBuilder = null; 126 | $currentKey = 0; 127 | 128 | while ($this->xml->read()) { 129 | if (\XMLReader::ELEMENT === $this->xml->nodeType) { 130 | switch ($this->xml->name) { 131 | case 'row': 132 | $currentKey = (int)$this->xml->getAttribute('r'); 133 | $rowBuilder = $this->rowBuilderFactory->create(); 134 | break; 135 | case 'c': 136 | $columnIndex = $this->columnIndexTransformer->transform($this->xml->getAttribute('r')); 137 | $style = $this->getValue($this->xml->getAttribute('s')); 138 | $type = $this->getValue($this->xml->getAttribute('t')); 139 | break; 140 | case 'v': 141 | $rowBuilder->addValue( 142 | $columnIndex, 143 | $this->valueTransformer->transform($this->xml->readString(), $type, $style) 144 | ); 145 | break; 146 | case 'is': 147 | $rowBuilder->addValue($columnIndex, $this->xml->readString()); 148 | break; 149 | } 150 | } elseif (\XMLReader::END_ELEMENT === $this->xml->nodeType) { 151 | switch ($this->xml->name) { 152 | case 'row': 153 | $currentValue = $rowBuilder->getData(); 154 | if (count($currentValue)) { 155 | $this->currentKey = $currentKey; 156 | $this->currentValue = $currentValue; 157 | $this->valid = true; 158 | 159 | return; 160 | } 161 | break; 162 | case 'sheetData': 163 | break 2; 164 | } 165 | } 166 | } 167 | } 168 | 169 | /** 170 | * {@inheritdoc} 171 | */ 172 | public function rewind() 173 | { 174 | if ($this->xml) { 175 | $this->xml->close(); 176 | } 177 | $this->xml = new \XMLReader(); 178 | $this->xml->open($this->path); 179 | $this->next(); 180 | } 181 | 182 | /** 183 | * {@inheritdoc} 184 | */ 185 | public function valid() 186 | { 187 | return $this->valid; 188 | } 189 | 190 | /** 191 | * Returns a normalized attribute value 192 | * 193 | * @param string $value 194 | * 195 | * @return string 196 | */ 197 | protected function getValue($value) 198 | { 199 | return null === $value ? '' : $value; 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/Xlsx/RowIteratorFactory.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2014 Akeneo SAS (http://www.akeneo.com) 10 | * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) 11 | */ 12 | class RowIteratorFactory 13 | { 14 | 15 | /** 16 | * 17 | * @var RowBuilderFactory 18 | */ 19 | protected $rowBuilderFactory; 20 | 21 | /** 22 | * 23 | * @var ColumnIndexTransformer 24 | */ 25 | protected $columnIndexTransformer; 26 | 27 | /** 28 | * 29 | * @var string 30 | */ 31 | protected $iteratorClass; 32 | 33 | /** 34 | * Constructor 35 | * 36 | * @param RowBuilderFactory $rowBuilderFactory 37 | * @param ColumnIndexTransformer $columnIndexTransformer 38 | * @param string $iteratorClass the class for row iterators 39 | */ 40 | public function __construct( 41 | RowBuilderFactory $rowBuilderFactory, 42 | ColumnIndexTransformer $columnIndexTransformer, 43 | $iteratorClass 44 | ) { 45 | $this->rowBuilderFactory = $rowBuilderFactory; 46 | $this->columnIndexTransformer = $columnIndexTransformer; 47 | $this->iteratorClass = $iteratorClass; 48 | } 49 | 50 | /** 51 | * Creates a row iterator for the XML given worksheet file 52 | * 53 | * @param ValueTransformer $valueTransformer the value transformer for the spreadsheet 54 | * @param string $path the path to the extracted XML worksheet file 55 | * @param array $options options specific to the format 56 | * @param Archive $archive The Archive from which the path was extracted 57 | * 58 | * @return RowIterator 59 | */ 60 | public function create(ValueTransformer $valueTransformer, $path, array $options, Archive $archive) 61 | { 62 | return new $this->iteratorClass( 63 | $this->rowBuilderFactory, 64 | $this->columnIndexTransformer, 65 | $valueTransformer, 66 | $path, 67 | $options, 68 | $archive 69 | ); 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/Xlsx/SharedStrings.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2014 Akeneo SAS (http://www.akeneo.com) 10 | * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) 11 | */ 12 | class SharedStrings extends AbstractXMLDictionnary 13 | { 14 | /** 15 | * @var int 16 | */ 17 | protected $currentIndex = -1; 18 | 19 | /** 20 | * Reads the next value in the file 21 | */ 22 | protected function readNext() 23 | { 24 | $xml = $this->getXMLReader(); 25 | while ($xml->read()) { 26 | if (\XMLReader::ELEMENT === $xml->nodeType) { 27 | switch ($xml->name) { 28 | case 'si': 29 | $this->currentIndex++; 30 | break; 31 | case 't': 32 | $this->values[$this->currentIndex] = $xml->readString(); 33 | 34 | return; 35 | } 36 | } 37 | } 38 | 39 | $this->valid = false; 40 | $this->closeXMLReader(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Xlsx/SharedStringsLoader.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2014 Akeneo SAS (http://www.akeneo.com) 10 | * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) 11 | */ 12 | class SharedStringsLoader 13 | { 14 | 15 | /** 16 | * 17 | * @var string 18 | */ 19 | protected $sharedStringsClass; 20 | 21 | /** 22 | * Constructor 23 | * 24 | * @param string $sharedStringsClass The class for created objects 25 | */ 26 | public function __construct($sharedStringsClass) 27 | { 28 | $this->sharedStringsClass = $sharedStringsClass; 29 | } 30 | 31 | /** 32 | * Creates a SharedStrings from the archive 33 | * 34 | * @param string $path 35 | * @param Archive $archive The Archive from which the path was extracted 36 | * 37 | * @return SharedStrings 38 | */ 39 | public function open($path, Archive $archive) 40 | { 41 | return new $this->sharedStringsClass($path, $archive); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/Xlsx/Spreadsheet.php: -------------------------------------------------------------------------------- 1 | 11 | * @copyright 2014 Akeneo SAS (http://www.akeneo.com) 12 | * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) 13 | */ 14 | class Spreadsheet implements SpreadsheetInterface 15 | { 16 | 17 | /** 18 | * @staticvar string Path to the relationships file inside the XLSX archive 19 | */ 20 | const RELATIONSHIPS_PATH = 'xl/_rels/workbook.xml.rels'; 21 | 22 | /** 23 | * @staticvar string Path to the spreadsheets file inside the XLSX archive 24 | */ 25 | const WORKBOOK_PATH = 'xl/workbook.xml'; 26 | 27 | /** 28 | * @var RelationshipsLoader 29 | */ 30 | protected $relationshipsLoader; 31 | 32 | /** 33 | * @var ValueTransformerFactory 34 | */ 35 | protected $valueTransformerFactory; 36 | 37 | /** 38 | * 39 | * @var SharedStringsLoader 40 | */ 41 | protected $sharedStringsLoader; 42 | 43 | /** 44 | * 45 | * @var RowIteratorFactory 46 | */ 47 | protected $rowIteratorFactory; 48 | 49 | /** 50 | * @var Archive 51 | */ 52 | protected $archive; 53 | 54 | /** 55 | * @var StylesLoader 56 | */ 57 | protected $stylesLoader; 58 | 59 | /** 60 | * @var WorksheetListReader 61 | */ 62 | protected $worksheetListReader; 63 | 64 | /** 65 | * @var Relationships 66 | */ 67 | private $relationships; 68 | 69 | /** 70 | * @var ValueTransformer 71 | */ 72 | private $valueTransformer; 73 | 74 | /** 75 | * @var SharedStrings 76 | */ 77 | private $sharedStrings; 78 | 79 | /** 80 | * @var array 81 | */ 82 | private $worksheetPaths; 83 | 84 | /** 85 | * @var Styles 86 | */ 87 | private $styles; 88 | 89 | /** 90 | * Constructor 91 | * 92 | * @param Archive $archive 93 | * @param RelationshipsLoader $relationshipsLoader 94 | * @param SharedStringsLoader $sharedStringsLoader 95 | * @param StylesLoader $stylesLoader 96 | * @param WorksheetListReader $worksheetListReader 97 | * @param ValueTransformerFactory $valueTransformerFactory 98 | * @param RowIteratorFactory $rowIteratorFactory 99 | */ 100 | public function __construct( 101 | Archive $archive, 102 | RelationshipsLoader $relationshipsLoader, 103 | SharedStringsLoader $sharedStringsLoader, 104 | StylesLoader $stylesLoader, 105 | WorksheetListReader $worksheetListReader, 106 | ValueTransformerFactory $valueTransformerFactory, 107 | RowIteratorFactory $rowIteratorFactory 108 | ) { 109 | $this->archive = $archive; 110 | $this->relationshipsLoader = $relationshipsLoader; 111 | $this->sharedStringsLoader = $sharedStringsLoader; 112 | $this->stylesLoader = $stylesLoader; 113 | $this->worksheetListReader = $worksheetListReader; 114 | $this->valueTransformerFactory = $valueTransformerFactory; 115 | $this->rowIteratorFactory = $rowIteratorFactory; 116 | } 117 | 118 | /** 119 | * {@inheritdoc} 120 | */ 121 | public function getWorksheets() 122 | { 123 | return array_keys($this->getWorksheetPaths()); 124 | } 125 | 126 | /** 127 | * {@inheritdoc} 128 | */ 129 | public function createRowIterator($worksheetIndex, array $options = []) 130 | { 131 | $paths = array_values($this->getWorksheetPaths()); 132 | 133 | return $this->rowIteratorFactory->create( 134 | $this->getValueTransformer(), 135 | $this->archive->extract($paths[$worksheetIndex]), 136 | $options, 137 | $this->archive 138 | ); 139 | } 140 | 141 | /** 142 | * {@inheritdoc} 143 | */ 144 | public function getWorksheetIndex($name) 145 | { 146 | return array_search($name, $this->getWorksheets()); 147 | } 148 | 149 | /** 150 | * @return Relationships 151 | */ 152 | protected function getRelationships() 153 | { 154 | if (!$this->relationships) { 155 | $path = $this->archive->extract(static::RELATIONSHIPS_PATH); 156 | $this->relationships = $this->relationshipsLoader->open($path); 157 | } 158 | 159 | return $this->relationships; 160 | } 161 | 162 | /** 163 | * @return ValueTransformer 164 | */ 165 | protected function getValueTransformer() 166 | { 167 | if (!$this->valueTransformer) { 168 | $this->valueTransformer = $this->valueTransformerFactory->create( 169 | $this->getSharedStrings(), 170 | $this->getStyles() 171 | ); 172 | } 173 | 174 | return $this->valueTransformer; 175 | } 176 | 177 | /** 178 | * @return SharedStrings 179 | */ 180 | protected function getSharedStrings() 181 | { 182 | if (!$this->sharedStrings) { 183 | $path = $this->archive->extract($this->relationships->getSharedStringsPath()); 184 | $this->sharedStrings = $this->sharedStringsLoader->open($path, $this->archive); 185 | } 186 | 187 | return $this->sharedStrings; 188 | } 189 | 190 | /** 191 | * @return array 192 | */ 193 | protected function getWorksheetPaths() 194 | { 195 | if (!$this->worksheetPaths) { 196 | $path = $this->archive->extract(static::WORKBOOK_PATH); 197 | $this->worksheetPaths = $this->worksheetListReader->getWorksheetPaths($this->getRelationships(), $path); 198 | } 199 | 200 | return $this->worksheetPaths; 201 | } 202 | 203 | /** 204 | * @return Styles 205 | */ 206 | protected function getStyles() 207 | { 208 | if (!$this->styles) { 209 | $path = $this->archive->extract($this->relationships->getStylesPath()); 210 | $this->styles = $this->stylesLoader->open($path, $this->archive); 211 | } 212 | 213 | return $this->styles; 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /src/Xlsx/SpreadsheetLoader.php: -------------------------------------------------------------------------------- 1 | 11 | * @copyright 2014 Akeneo SAS (http://www.akeneo.com) 12 | * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) 13 | */ 14 | class SpreadsheetLoader implements SpreadsheetLoaderInterface 15 | { 16 | /** 17 | * @var string 18 | */ 19 | protected $spreadsheetClass; 20 | 21 | /** 22 | * @var RelationshipsLoader 23 | */ 24 | protected $relationshipsLoader; 25 | 26 | /** 27 | * @var SharedStringsLoader 28 | */ 29 | protected $sharedStringsLoader; 30 | 31 | /** 32 | * @var StylesLoader 33 | */ 34 | protected $stylesLoader; 35 | 36 | /** 37 | * @var WorksheetListReader 38 | */ 39 | protected $worksheetListReader; 40 | 41 | /** 42 | * @var ValueTransformerFactory 43 | */ 44 | protected $valueTransformerFactory; 45 | 46 | /** 47 | * @var RowIteratorFactory 48 | */ 49 | protected $rowIteratorFactory; 50 | 51 | /** 52 | * @var ArchiveLoader 53 | */ 54 | protected $archiveLoader; 55 | 56 | /** 57 | * Constructor 58 | * 59 | * @param ArchiveLoader $archiveLoader 60 | * @param RelationshipsLoader $relationshipsLoader 61 | * @param SharedStringsLoader $sharedStringsLoader 62 | * @param StylesLoader $stylesLoader 63 | * @param WorksheetListReader $worksheetListReader 64 | * @param ValueTransformerFactory $valueTransformerFactory 65 | * @param RowIteratorFactory $rowIteratorFactory 66 | * @param string $spreadsheetClass 67 | */ 68 | public function __construct( 69 | ArchiveLoader $archiveLoader, 70 | RelationshipsLoader $relationshipsLoader, 71 | SharedStringsLoader $sharedStringsLoader, 72 | StylesLoader $stylesLoader, 73 | WorksheetListReader $worksheetListReader, 74 | ValueTransformerFactory $valueTransformerFactory, 75 | RowIteratorFactory $rowIteratorFactory, 76 | $spreadsheetClass 77 | ) { 78 | $this->relationshipsLoader = $relationshipsLoader; 79 | $this->sharedStringsLoader = $sharedStringsLoader; 80 | $this->stylesLoader = $stylesLoader; 81 | $this->worksheetListReader = $worksheetListReader; 82 | $this->valueTransformerFactory = $valueTransformerFactory; 83 | $this->rowIteratorFactory = $rowIteratorFactory; 84 | $this->archiveLoader = $archiveLoader; 85 | $this->spreadsheetClass = $spreadsheetClass; 86 | } 87 | 88 | /** 89 | * {@inheritdoc} 90 | */ 91 | public function open($path, $type = null) 92 | { 93 | $archive = $this->archiveLoader->open($path); 94 | 95 | return new $this->spreadsheetClass( 96 | $archive, 97 | $this->relationshipsLoader, 98 | $this->sharedStringsLoader, 99 | $this->stylesLoader, 100 | $this->worksheetListReader, 101 | $this->valueTransformerFactory, 102 | $this->rowIteratorFactory 103 | ); 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /src/Xlsx/Styles.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2014 Akeneo SAS (http://www.akeneo.com) 10 | * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) 11 | */ 12 | class Styles extends AbstractXMLDictionnary 13 | { 14 | /** 15 | * @staticvar int Default format 16 | */ 17 | const FORMAT_DEFAULT = 0; 18 | 19 | /** 20 | * @staticvar int Date format 21 | */ 22 | const FORMAT_DATE = 1; 23 | 24 | /** 25 | * @var array 26 | */ 27 | protected $nativeDateFormats = [14, 15, 16, 17, 18, 19, 20, 21, 22]; 28 | 29 | /** 30 | * @var array 31 | */ 32 | protected $numberFormats = []; 33 | 34 | /** 35 | * @var boolean 36 | */ 37 | protected $inXfs; 38 | 39 | /** 40 | * {@inheritdoc} 41 | */ 42 | protected function readNext() 43 | { 44 | $xml = $this->getXMLReader(); 45 | while ($xml->read()) { 46 | if (\XMLReader::END_ELEMENT === $xml->nodeType && 'cellXfs' === $xml->name) { 47 | break; 48 | } elseif (\XMLReader::ELEMENT === $xml->nodeType && 'cellXfs' === $xml->name) { 49 | $this->inXfs = true; 50 | } elseif ($this->inXfs && \XMLReader::ELEMENT === $xml->nodeType && 'xf' === $xml->name) { 51 | $fmtId = $xml->getAttribute('numFmtId'); 52 | if (isset($this->numberFormats[$fmtId])) { 53 | $value = $this->numberFormats[$fmtId]; 54 | } elseif (in_array($fmtId, $this->nativeDateFormats)) { 55 | $value = static::FORMAT_DATE; 56 | } else { 57 | $value = static::FORMAT_DEFAULT; 58 | } 59 | $this->values[] = $value; 60 | 61 | return; 62 | } 63 | } 64 | $this->valid = false; 65 | $this->closeXMLReader(); 66 | } 67 | 68 | /** 69 | * {@inheritdoc} 70 | */ 71 | protected function createXMLReader() 72 | { 73 | $xml = parent::createXMLReader(); 74 | $needsRewind = false; 75 | while ($xml->read()) { 76 | if (\XMLReader::END_ELEMENT === $xml->nodeType && 'numFmts' === $xml->name) { 77 | break; 78 | } elseif (\XMLReader::ELEMENT === $xml->nodeType) { 79 | switch ($xml->name) { 80 | case 'numFmt': 81 | $this->numberFormats[$xml->getAttribute('numFmtId')] = 82 | preg_match('{^(\[\$[[:alpha:]]*-[0-9A-F]*\])*[hmsdy]}i', $xml->getAttribute('formatCode')) 83 | ? static::FORMAT_DATE 84 | : static::FORMAT_DEFAULT; 85 | break; 86 | case 'cellXfs': 87 | $needsRewind = true; 88 | break; 89 | } 90 | } 91 | } 92 | if ($needsRewind) { 93 | $xml->close(); 94 | $xml = parent::createXMLReader(); 95 | } 96 | 97 | return $xml; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/Xlsx/StylesLoader.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2014 Akeneo SAS (http://www.akeneo.com) 10 | * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) 11 | */ 12 | class StylesLoader 13 | { 14 | /** 15 | * @var string 16 | */ 17 | protected $class; 18 | 19 | /** 20 | * Constructor 21 | * 22 | * @param string $class The class for created objects 23 | */ 24 | public function __construct($class) 25 | { 26 | $this->class = $class; 27 | } 28 | 29 | /** 30 | * Creates a Styles from the archive 31 | * 32 | * @param string $path 33 | * @param Archive $archive The Archive from which the path was extracted 34 | * 35 | * @return Styles 36 | */ 37 | public function open($path, Archive $archive) 38 | { 39 | return new $this->class($path, $archive); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Xlsx/ValueTransformer.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2014 Akeneo SAS (http://www.akeneo.com) 10 | * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) 11 | */ 12 | class ValueTransformer 13 | { 14 | /** 15 | * @var DateTransformer 16 | */ 17 | protected $dateTransformer; 18 | 19 | /** 20 | * @var SharedStrings 21 | */ 22 | protected $sharedStrings; 23 | 24 | /** 25 | * @var Styles 26 | */ 27 | protected $styles; 28 | 29 | /** 30 | * @staticvar string Boolean type 31 | */ 32 | const TYPE_BOOL = 'b'; 33 | 34 | /** 35 | * @staticvar string Number type 36 | */ 37 | const TYPE_NUMBER = 'n'; 38 | 39 | /** 40 | * @staticvar string Error type 41 | */ 42 | const TYPE_ERROR = 'e'; 43 | 44 | /** 45 | * @staticvar string Shared string type 46 | */ 47 | const TYPE_SHARED_STRING = 's'; 48 | 49 | /** 50 | * @staticvar string String type 51 | */ 52 | const TYPE_STRING = 'str'; 53 | 54 | /** 55 | * @staticvar string Inline string type 56 | */ 57 | const TYPE_INLINE_STRING = 'inlineStr'; 58 | 59 | /** 60 | * Constructor 61 | * 62 | * @param DateTransformer $dateTransformer 63 | * @param SharedStrings $sharedStrings 64 | * @param Styles $styles 65 | */ 66 | public function __construct(DateTransformer $dateTransformer, SharedStrings $sharedStrings, Styles $styles) 67 | { 68 | $this->dateTransformer = $dateTransformer; 69 | $this->sharedStrings = $sharedStrings; 70 | $this->styles = $styles; 71 | } 72 | 73 | /** 74 | * Formats a value 75 | * 76 | * @param string $value The value which should be transformed 77 | * @param string $type The type of the value 78 | * @param string $style The style of the value 79 | * 80 | * @return mixed 81 | */ 82 | public function transform($value, $type, $style) 83 | { 84 | switch ($type) { 85 | case static::TYPE_BOOL: 86 | return ('1' === $value); 87 | case static::TYPE_SHARED_STRING: 88 | return rtrim($this->sharedStrings->get($value)); 89 | case '': 90 | case static::TYPE_NUMBER: 91 | return $style && (Styles::FORMAT_DATE === $this->styles->get($style)) 92 | ? $this->dateTransformer->transform($value) 93 | : $value * 1; 94 | default: 95 | return rtrim($value); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/Xlsx/ValueTransformerFactory.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2014 Akeneo SAS (http://www.akeneo.com) 10 | * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) 11 | */ 12 | class ValueTransformerFactory 13 | { 14 | /** 15 | * 16 | * @var string 17 | */ 18 | protected $transformerClass; 19 | 20 | /** 21 | * 22 | * @var DateTransformer 23 | */ 24 | protected $dateTransformer; 25 | 26 | /** 27 | * Constructor 28 | * 29 | * @param DateTransformer $dateTransformer 30 | * @param string $transformerClass The class of the created objects 31 | */ 32 | public function __construct(DateTransformer $dateTransformer, $transformerClass) 33 | { 34 | $this->dateTransformer = $dateTransformer; 35 | $this->transformerClass = $transformerClass; 36 | } 37 | 38 | /** 39 | * Creates a value transformer 40 | * 41 | * @param SharedStrings $sharedStrings 42 | * @param Styles $styles 43 | * 44 | * @return ValueTransformer 45 | */ 46 | public function create(SharedStrings $sharedStrings, Styles $styles) 47 | { 48 | return new $this->transformerClass($this->dateTransformer, $sharedStrings, $styles); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Xlsx/WorksheetListReader.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2014 Akeneo SAS (http://www.akeneo.com) 10 | * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) 11 | */ 12 | class WorksheetListReader 13 | { 14 | /** 15 | * Returns the list of worksheets inside the archive 16 | * 17 | * The keys of the array should be the titles of the worksheets 18 | * The values of the array are the names of the XML worksheet files inside the archive 19 | * 20 | * @param Relationships $relationships 21 | * @param string $path 22 | * 23 | * @return array 24 | */ 25 | public function getWorksheetPaths(Relationships $relationships, $path) 26 | { 27 | $xml = new \XMLReader(); 28 | $xml->open($path); 29 | $paths = []; 30 | while ($xml->read()) { 31 | if (\XMLReader::ELEMENT === $xml->nodeType && 'sheet' === $xml->name) { 32 | $rId = $xml->getAttributeNs( 33 | 'id', 34 | 'http://schemas.openxmlformats.org/officeDocument/2006/relationships' 35 | ); 36 | $paths[$xml->getAttribute('name')] = $relationships->getWorksheetPath($rId); 37 | } 38 | } 39 | 40 | return $paths; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Xlsx/XlsxParser.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2014 Akeneo SAS (http://www.akeneo.com) 10 | * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) 11 | */ 12 | class XlsxParser 13 | { 14 | /** 15 | * @staticvar string the name of the format 16 | */ 17 | const FORMAT_NAME = 'xlsx'; 18 | 19 | /** 20 | * @staticvar string the name of the macro format 21 | */ 22 | const MACRO_FORMAT_NAME = 'xlsm'; 23 | 24 | /** 25 | * @staticvar string Archive class 26 | */ 27 | const ARCHIVE_CLASS = 'Akeneo\Component\SpreadsheetParser\Xlsx\Archive'; 28 | 29 | /** 30 | * @staticvar string Relationships class 31 | */ 32 | const RELATIONSHIPS_CLASS = 'Akeneo\Component\SpreadsheetParser\Xlsx\Relationships'; 33 | 34 | /** 35 | * @staticvar string RowBuilder class 36 | */ 37 | const ROW_BUILDER_CLASS = 'Akeneo\Component\SpreadsheetParser\Xlsx\RowBuilder'; 38 | 39 | /** 40 | * @staticvar string RowIterator class 41 | */ 42 | const ROW_ITERATOR_CLASS = 'Akeneo\Component\SpreadsheetParser\Xlsx\RowIterator'; 43 | 44 | /** 45 | * @staticvar string SharedStrings class 46 | */ 47 | const SHARED_STRINGS_CLASS = 'Akeneo\Component\SpreadsheetParser\Xlsx\SharedStrings'; 48 | 49 | /** 50 | * @staticvar string Styles class 51 | */ 52 | const STYLES_CLASS = 'Akeneo\Component\SpreadsheetParser\Xlsx\Styles'; 53 | 54 | /** 55 | * @staticvar string ValueTransformer class 56 | */ 57 | const VALUE_TRANSFORMER_CLASS = 'Akeneo\Component\SpreadsheetParser\Xlsx\ValueTransformer'; 58 | 59 | /** 60 | * @staticvar string Spreadsheet class 61 | */ 62 | const WORKBOOK_CLASS = 'Akeneo\Component\SpreadsheetParser\Xlsx\Spreadsheet'; 63 | 64 | /** 65 | * @var SpreadsheetLoader 66 | */ 67 | private static $spreadsheetLoader; 68 | 69 | /** 70 | * Opens an XLSX file 71 | * 72 | * @param string $path 73 | * 74 | * @return Spreadsheet 75 | */ 76 | public static function open($path) 77 | { 78 | return static::getSpreadsheetLoader()->open($path); 79 | } 80 | 81 | /** 82 | * @return SpreadsheetLoader 83 | */ 84 | public static function getSpreadsheetLoader() 85 | { 86 | if (!isset(self::$spreadsheetLoader)) { 87 | self::$spreadsheetLoader = new SpreadsheetLoader( 88 | static::createArchiveLoader(), 89 | static::createRelationshipsLoader(), 90 | static::createSharedStringsLoader(), 91 | static::createStylesLoader(), 92 | static::createWorksheetListReader(), 93 | static::createValueTransformerFactory(), 94 | static::createRowIteratorFactory(), 95 | static::WORKBOOK_CLASS 96 | ); 97 | } 98 | 99 | return self::$spreadsheetLoader; 100 | } 101 | 102 | /** 103 | * @return ArchiveLoader 104 | */ 105 | protected static function createArchiveLoader() 106 | { 107 | return new ArchiveLoader(static::ARCHIVE_CLASS); 108 | } 109 | 110 | /** 111 | * @return RelationshipsLoader 112 | */ 113 | protected static function createRelationshipsLoader() 114 | { 115 | return new RelationshipsLoader(static::RELATIONSHIPS_CLASS); 116 | } 117 | 118 | /** 119 | * @return SharedStringsLoader 120 | */ 121 | protected static function createSharedStringsLoader() 122 | { 123 | return new SharedStringsLoader(static::SHARED_STRINGS_CLASS); 124 | } 125 | 126 | /** 127 | * @return StylesLoader 128 | */ 129 | protected static function createStylesLoader() 130 | { 131 | return new StylesLoader(static::STYLES_CLASS); 132 | } 133 | 134 | /** 135 | * @return WorksheetListReader 136 | */ 137 | protected static function createWorksheetListReader() 138 | { 139 | return new WorksheetListReader(); 140 | } 141 | 142 | /** 143 | * @return ValueTransformerFactory 144 | */ 145 | protected static function createValueTransformerFactory() 146 | { 147 | return new ValueTransformerFactory(static::createDateTransformer(), static::VALUE_TRANSFORMER_CLASS); 148 | } 149 | 150 | /** 151 | * @return DateTransformer 152 | */ 153 | protected static function createDateTransformer() 154 | { 155 | return new DateTransformer(); 156 | } 157 | 158 | /** 159 | * @return RowBuilderFactory 160 | */ 161 | protected static function createRowBuilderFactory() 162 | { 163 | return new RowBuilderFactory(static::ROW_BUILDER_CLASS); 164 | } 165 | 166 | /** 167 | * @return ColumnIndexTransformer 168 | */ 169 | protected static function createColumnIndexTransformer() 170 | { 171 | return new ColumnIndexTransformer(); 172 | } 173 | 174 | /** 175 | * @return RowIteratorFactory 176 | */ 177 | protected static function createRowIteratorFactory() 178 | { 179 | return new RowIteratorFactory( 180 | static::createRowBuilderFactory(), 181 | static::createColumnIndexTransformer(), 182 | static::ROW_ITERATOR_CLASS 183 | ); 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /tests/CsvTest.php: -------------------------------------------------------------------------------- 1 | 11 | * @copyright 2014 Akeneo SAS (http://www.akeneo.com) 12 | * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) 13 | */ 14 | class CsvTest extends TestCase 15 | { 16 | public function testReadFile() 17 | { 18 | $spreadsheet = SpreadsheetParser::open(__DIR__ . '/fixtures/test.csv'); 19 | $this->assertEquals(['default'], $spreadsheet->getWorksheets()); 20 | $this->assertIteratesThrough( 21 | [ 22 | 1 => ['value', 'enclosed value', '15'], 23 | 2 => ['', 'value2', ''] 24 | ], 25 | $spreadsheet->createRowIterator(0) 26 | ); 27 | } 28 | 29 | public function testReadFileWithForcedFormat() 30 | { 31 | $spreadsheet = SpreadsheetParser::open(__DIR__ . '/fixtures/test.txt', CsvParser::FORMAT_NAME); 32 | $this->assertEquals(['default'], $spreadsheet->getWorksheets()); 33 | $this->assertIteratesThrough( 34 | [ 35 | 1 => ['value', 'enclosed value', '15'], 36 | 2 => ['', 'value2', ''] 37 | ], 38 | $spreadsheet->createRowIterator(0) 39 | ); 40 | } 41 | 42 | public function testCsvParserClass() 43 | { 44 | $spreadsheet = CsvParser::open(__DIR__ . '/fixtures/test.txt'); 45 | $this->assertIteratesThrough( 46 | [ 47 | 1 => ['value', 'enclosed value', '15'], 48 | 2 => ['', 'value2', ''] 49 | ], 50 | $spreadsheet->createRowIterator(0) 51 | ); 52 | } 53 | 54 | protected function assertIteratesThrough($values, $iterator) 55 | { 56 | $valuesIterator = new ArrayIterator($values); 57 | $valuesIterator->rewind(); 58 | foreach ($iterator as $key => $row) { 59 | $this->assertTrue($valuesIterator->valid()); 60 | $this->assertEquals($valuesIterator->key(), $key); 61 | $this->assertEquals($valuesIterator->current(), $row); 62 | $valuesIterator->next(); 63 | } 64 | $this->assertFalse($valuesIterator->valid()); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tests/XlsxTest.php: -------------------------------------------------------------------------------- 1 | 11 | * @copyright 2014 Akeneo SAS (http://www.akeneo.com) 12 | * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) 13 | */ 14 | class XlsxTest extends TestCase 15 | { 16 | public function testLibreOfficeFile() 17 | { 18 | $spreadsheet = SpreadsheetParser::open(__DIR__ . '/fixtures/libreoffice.xlsx'); 19 | $this->assertEquals(['Sheet1', 'Sheet2'], $spreadsheet->getWorksheets()); 20 | $this->assertIteratesThrough( 21 | [ 22 | 2 => ['value1', '', 'value2'], 23 | 3 => ['value3', '2010-12-05 00:00', 154], 24 | 5 => ['test', '2006-08-12 15:46'] 25 | ], 26 | $spreadsheet->createRowIterator(0) 27 | ); 28 | $this->assertIteratesThrough( 29 | [ 30 | 4 => ['value7', '', 'value11'] 31 | ], 32 | $spreadsheet->createRowIterator(1) 33 | ); 34 | } 35 | 36 | public function testXlsParserClass() 37 | { 38 | $spreadsheet = XlsxParser::open(__DIR__ . '/fixtures/libreoffice.xlsx'); 39 | $this->assertEquals(['Sheet1', 'Sheet2'], $spreadsheet->getWorksheets()); 40 | $this->assertIteratesThrough( 41 | [ 42 | 2 => ['value1', '', 'value2'], 43 | 3 => ['value3', '2010-12-05 00:00', 154], 44 | 5 => ['test', '2006-08-12 15:46'] 45 | ], 46 | $spreadsheet->createRowIterator(0) 47 | ); 48 | $this->assertIteratesThrough( 49 | [ 50 | 4 => ['value7', '', 'value11'] 51 | ], 52 | $spreadsheet->createRowIterator(1) 53 | ); 54 | } 55 | 56 | public function testReadSameFileTwice() 57 | { 58 | $spreadsheet = SpreadsheetParser::open(__DIR__ . '/fixtures/libreoffice.xlsx'); 59 | $this->assertEquals(['Sheet1', 'Sheet2'], $spreadsheet->getWorksheets()); 60 | $this->assertIteratesThrough( 61 | [ 62 | 2 => ['value1', '', 'value2'], 63 | 3 => ['value3', '2010-12-05 00:00', 154], 64 | 5 => ['test', '2006-08-12 15:46'] 65 | ], 66 | $spreadsheet->createRowIterator(0) 67 | ); 68 | $this->assertIteratesThrough( 69 | [ 70 | 2 => ['value1', '', 'value2'], 71 | 3 => ['value3', '2010-12-05 00:00', 154], 72 | 5 => ['test', '2006-08-12 15:46'] 73 | ], 74 | $spreadsheet->createRowIterator(0) 75 | ); 76 | } 77 | 78 | public function testMsOfficeFile() 79 | { 80 | $spreadsheet = SpreadsheetParser::open(__DIR__ . '/fixtures/msoffice.xlsx'); 81 | $this->assertEquals(['Feuil1', 'Feuil2', 'Feuil3'], $spreadsheet->getWorksheets()); 82 | $this->assertIteratesThrough( 83 | [ 84 | 3 => ['value1', '', '2014-12-15 00:00', '2015-01-15 12:16'], 85 | 5 => ['', 'value5'] 86 | ], 87 | $spreadsheet->createRowIterator(0) 88 | ); 89 | $this->assertIteratesThrough( 90 | [ 91 | 6 => ['', 'test1'], 92 | ], 93 | $spreadsheet->createRowIterator(1) 94 | ); 95 | $this->assertIteratesThrough( 96 | [ 97 | 1 => ['', '', '', 'test4'], 98 | ], 99 | $spreadsheet->createRowIterator(2) 100 | ); 101 | } 102 | 103 | public function testMacroMsOfficeFile() 104 | { 105 | $spreadsheet = SpreadsheetParser::open(__DIR__ . '/fixtures/msoffice.xlsm'); 106 | $this->assertEquals(['Feuil1', 'Feuil2', 'Feuil3'], $spreadsheet->getWorksheets()); 107 | $this->assertIteratesThrough( 108 | [ 109 | 3 => ['value1', '', '2014-12-15 00:00', '2015-01-15 12:16'], 110 | 5 => ['', 'value5'] 111 | ], 112 | $spreadsheet->createRowIterator(0) 113 | ); 114 | $this->assertIteratesThrough( 115 | [ 116 | 6 => ['', 'test1'], 117 | ], 118 | $spreadsheet->createRowIterator(1) 119 | ); 120 | $this->assertIteratesThrough( 121 | [ 122 | 1 => ['', '', '', 'test4'], 123 | ], 124 | $spreadsheet->createRowIterator(2) 125 | ); 126 | } 127 | 128 | 129 | protected function assertIteratesThrough($values, $iterator) 130 | { 131 | $valuesIterator = new ArrayIterator($values); 132 | $valuesIterator->rewind(); 133 | foreach ($iterator as $key => $row) { 134 | foreach ($row as &$value) { 135 | if ($value instanceof DateTime) { 136 | $value = $value->format('Y-m-d H:i'); 137 | } 138 | } 139 | $this->assertTrue($valuesIterator->valid()); 140 | $this->assertEquals($valuesIterator->key(), $key); 141 | $this->assertEquals($valuesIterator->current(), $row); 142 | $valuesIterator->next(); 143 | } 144 | $this->assertFalse($valuesIterator->valid()); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /tests/fixtures/libreoffice.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akeneo-labs/spreadsheet-parser/4be5fdae57363819dcd80e84ae875c6402213b1b/tests/fixtures/libreoffice.xlsx -------------------------------------------------------------------------------- /tests/fixtures/msoffice.xlsm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akeneo-labs/spreadsheet-parser/4be5fdae57363819dcd80e84ae875c6402213b1b/tests/fixtures/msoffice.xlsm -------------------------------------------------------------------------------- /tests/fixtures/msoffice.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akeneo-labs/spreadsheet-parser/4be5fdae57363819dcd80e84ae875c6402213b1b/tests/fixtures/msoffice.xlsx -------------------------------------------------------------------------------- /tests/fixtures/test.csv: -------------------------------------------------------------------------------- 1 | value,"enclosed value",15 2 | ,value2, -------------------------------------------------------------------------------- /tests/fixtures/test.txt: -------------------------------------------------------------------------------- 1 | value,"enclosed value",15 2 | ,value2, --------------------------------------------------------------------------------