├── .gitignore ├── README.md ├── composer.json ├── composer.lock ├── phpunit.xml ├── src ├── Area.php ├── Areas.php ├── Camelot.php └── Exceptions │ ├── BackgroundLinesNotSupportedException.php │ ├── ColumnSeparatorsNotSupportedException.php │ ├── NotSupportedException.php │ └── PdfEncryptedException.php └── tests ├── Feature ├── AdvancedProcessingTest.php ├── EncryptedPdfTest.php ├── PagesTest.php ├── ParseExportTest.php └── TableHintsTest.php ├── Helpers └── CsvAssertions.php ├── TestCase.php └── files ├── background_lines_1.pdf ├── column_separators.pdf ├── copy_text.pdf ├── edge_tol.pdf ├── foo_lattice.pdf ├── health_protected.pdf ├── health_stream.pdf ├── row_tol.pdf ├── short_lines.pdf ├── strip.pdf ├── superscript.pdf ├── table_areas.pdf ├── table_region.pdf └── twotables_2.pdf /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | .idea/ 3 | .phpunit.result.cache 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # randomstate/camelot-php 2 | 3 | A PHP wrapper for Camelot, the python PDF table extraction library 4 | 5 | ## Installation 6 | 7 | `composer require randomstate/camelot-php` 8 | 9 | ## Usage 10 | 11 | The package adheres closely with the camelot CLI API Usage. 12 | Default output is in CSV format as a simple string. If you need to parse CSV strings we recommend the `league/csv` package (https://csv.thephpleague.com/) 13 | 14 | ```php 15 | extract(); 22 | 23 | $csv = Reader::createFromString($tables[0]); 24 | $allRecords = $csv->getRecords(); 25 | ``` 26 | 27 | ### Advanced Processing 28 | 29 | ##### Saving / Extracting 30 | **Note: No Camelot operations are run until one of these methods is run** 31 | ```php 32 | $camelot->extract(); // uses temporary files and automatically grabs the table contents for you from each 33 | $camelot->save('/path/to/my-file.csv'); // mirrors the behaviour of Camelot and saves files in the format /path/to/my-file-page-*-table-*.csv 34 | $camelot->plot(); // useful for debugging, it will plot it in a separate window (see Visual Debugging below) 35 | ``` 36 | 37 | ##### [Set Format](https://camelot-py.readthedocs.io/en/master/user/quickstart.html#read-the-pdf) 38 | ``` 39 | $camelot->json(); 40 | $camelot->csv(); 41 | $camelot->html(); 42 | $camelot->excel(); 43 | $camelot->sqlite(); 44 | ``` 45 | ##### [Specify Page Numbers](https://camelot-py.readthedocs.io/en/master/user/quickstart.html#specify-page-numbers) 46 | 47 | `$camelot->pages('1,2,3-4,8-end')` 48 | 49 | ##### [Reading encrypted PDFs](https://camelot-py.readthedocs.io/en/master/user/quickstart.html#reading-encrypted-pdfs) 50 | 51 | `$camelot->password('my-pass')` 52 | 53 | ##### [Processing background lines](https://camelot-py.readthedocs.io/en/master/user/advanced.html#process-background-lines) 54 | `$camelot->stream()->processBackgroundLines()` 55 | 56 | ##### [Visual debugging](https://camelot-py.readthedocs.io/en/master/user/advanced.html#visual-debugging) 57 | 58 | `$camelot->plot()` 59 | 60 | ##### [Specify table areas](https://camelot-py.readthedocs.io/en/master/user/advanced.html#specify-table-areas) 61 | 62 | ```php 63 | inAreas( 70 | Areas::from($xTopLeft, $yTopLeft, $xBottomRight, $yBottomRight) 71 | // ->add($xTopLeft2, $yTopLeft2, $xBottomRight2, $yBottomRight2) 72 | // ->add($xTopLeft3, $yTopLeft3, $xBottomRight3, $yBottomRight3) 73 | ); 74 | ``` 75 | 76 | ##### [Specify table regions](https://camelot-py.readthedocs.io/en/master/user/advanced.html#specify-table-regions) 77 | 78 | ```php 79 | inRegions( 86 | Areas::from($xTopLeft, $yTopLeft, $xBottomRight, $yBottomRight) 87 | // ->add($xTopLeft2, $yTopLeft2, $xBottomRight2, $yBottomRight2) 88 | // ->add($xTopLeft3, $yTopLeft3, $xBottomRight3, $yBottomRight3) 89 | ); 90 | ``` 91 | 92 | ##### [Specify column separators](https://camelot-py.readthedocs.io/en/master/user/advanced.html#specify-column-separators) 93 | 94 | `$camelot->stream()->setColumnSeparators($x1,$x2...)` 95 | 96 | ##### [Split text along separators](https://camelot-py.readthedocs.io/en/master/user/advanced.html#split-text-along-separators) 97 | 98 | `$camelot->split()` 99 | 100 | ##### [Flag superscripts and subscripts](https://camelot-py.readthedocs.io/en/master/user/advanced.html#flag-superscripts-and-subscripts) 101 | 102 | `$camelot->flagSize()` 103 | 104 | ##### [Strip characters from text](https://camelot-py.readthedocs.io/en/master/user/advanced.html#strip-characters-from-text) 105 | 106 | `$camelot->strip("\n")` 107 | 108 | ##### [Improve guessed table areas](https://camelot-py.readthedocs.io/en/master/user/advanced.html#improve-guessed-table-areas) 109 | 110 | `$camelot->setEdgeTolerance(500)` 111 | 112 | ##### [Improve guessed table rows](https://camelot-py.readthedocs.io/en/master/user/advanced.html#improve-guessed-table-rows) 113 | 114 | `$camelot->setRowTolerance(15)` 115 | 116 | ##### [Detect short lines](https://camelot-py.readthedocs.io/en/master/user/advanced.html#detect-short-lines) 117 | 118 | `$camelot->lineScale(20)` 119 | 120 | 121 | ##### [Shift text in spanning cells](https://camelot-py.readthedocs.io/en/master/user/advanced.html#shift-text-in-spanning-cells) 122 | 123 | `$camelot->shiftText('r', 'b')` 124 | 125 | ##### [Copy text in spanning cells](https://camelot-py.readthedocs.io/en/master/user/advanced.html#copy-text-in-spanning-cells) 126 | 127 | `$camelot->copyTextSpanningCells('r', 'b')` 128 | 129 | 130 | ## License 131 | 132 | MIT. Use at your own risk, we accept no liability for how this code is used. -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "randomstate/camelot-php", 3 | "description": "PHP Wrapper library for interfacing with the Camelot PDF table extraction library built in Python", 4 | "type": "library", 5 | "authors": [ 6 | { 7 | "name": "Connor Forsyth", 8 | "email": "connor@randomstate.co.uk" 9 | } 10 | ], 11 | "require": { 12 | "symfony/process": "^4.0|^5.0", 13 | "spatie/temporary-directory": "^1.2" 14 | }, 15 | "autoload": { 16 | "psr-4": { 17 | "RandomState\\Camelot\\": "src/" 18 | } 19 | }, 20 | "autoload-dev": { 21 | "psr-4": { 22 | "RandomState\\Camelot\\Tests\\": "tests/" 23 | } 24 | }, 25 | "require-dev": { 26 | "phpunit/phpunit": "^8.5", 27 | "league/csv": "^9.4" 28 | }, 29 | "license": "MIT" 30 | } 31 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "0f35e614384fdc9d42ad84de6649e2a1", 8 | "packages": [ 9 | { 10 | "name": "spatie/temporary-directory", 11 | "version": "1.2.1", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/spatie/temporary-directory.git", 15 | "reference": "3e51af9a8361f85cffc1fb2c52135f3e064758cc" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/spatie/temporary-directory/zipball/3e51af9a8361f85cffc1fb2c52135f3e064758cc", 20 | "reference": "3e51af9a8361f85cffc1fb2c52135f3e064758cc", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "php": "^7.2" 25 | }, 26 | "require-dev": { 27 | "phpunit/phpunit": "^8.0" 28 | }, 29 | "type": "library", 30 | "autoload": { 31 | "psr-4": { 32 | "Spatie\\TemporaryDirectory\\": "src" 33 | } 34 | }, 35 | "notification-url": "https://packagist.org/downloads/", 36 | "license": [ 37 | "MIT" 38 | ], 39 | "authors": [ 40 | { 41 | "name": "Alex Vanderbist", 42 | "email": "alex@spatie.be", 43 | "homepage": "https://spatie.be", 44 | "role": "Developer" 45 | } 46 | ], 47 | "description": "Easily create, use and destroy temporary directories", 48 | "homepage": "https://github.com/spatie/temporary-directory", 49 | "keywords": [ 50 | "spatie", 51 | "temporary-directory" 52 | ], 53 | "time": "2019-08-28T06:53:51+00:00" 54 | }, 55 | { 56 | "name": "symfony/process", 57 | "version": "v5.0.1", 58 | "source": { 59 | "type": "git", 60 | "url": "https://github.com/symfony/process.git", 61 | "reference": "1568a2e8370fbc7416ef64eb5a698e4a05db5ff4" 62 | }, 63 | "dist": { 64 | "type": "zip", 65 | "url": "https://api.github.com/repos/symfony/process/zipball/1568a2e8370fbc7416ef64eb5a698e4a05db5ff4", 66 | "reference": "1568a2e8370fbc7416ef64eb5a698e4a05db5ff4", 67 | "shasum": "" 68 | }, 69 | "require": { 70 | "php": "^7.2.5" 71 | }, 72 | "type": "library", 73 | "extra": { 74 | "branch-alias": { 75 | "dev-master": "5.0-dev" 76 | } 77 | }, 78 | "autoload": { 79 | "psr-4": { 80 | "Symfony\\Component\\Process\\": "" 81 | }, 82 | "exclude-from-classmap": [ 83 | "/Tests/" 84 | ] 85 | }, 86 | "notification-url": "https://packagist.org/downloads/", 87 | "license": [ 88 | "MIT" 89 | ], 90 | "authors": [ 91 | { 92 | "name": "Fabien Potencier", 93 | "email": "fabien@symfony.com" 94 | }, 95 | { 96 | "name": "Symfony Community", 97 | "homepage": "https://symfony.com/contributors" 98 | } 99 | ], 100 | "description": "Symfony Process Component", 101 | "homepage": "https://symfony.com", 102 | "time": "2019-11-28T14:20:16+00:00" 103 | } 104 | ], 105 | "packages-dev": [ 106 | { 107 | "name": "doctrine/instantiator", 108 | "version": "1.3.0", 109 | "source": { 110 | "type": "git", 111 | "url": "https://github.com/doctrine/instantiator.git", 112 | "reference": "ae466f726242e637cebdd526a7d991b9433bacf1" 113 | }, 114 | "dist": { 115 | "type": "zip", 116 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/ae466f726242e637cebdd526a7d991b9433bacf1", 117 | "reference": "ae466f726242e637cebdd526a7d991b9433bacf1", 118 | "shasum": "" 119 | }, 120 | "require": { 121 | "php": "^7.1" 122 | }, 123 | "require-dev": { 124 | "doctrine/coding-standard": "^6.0", 125 | "ext-pdo": "*", 126 | "ext-phar": "*", 127 | "phpbench/phpbench": "^0.13", 128 | "phpstan/phpstan-phpunit": "^0.11", 129 | "phpstan/phpstan-shim": "^0.11", 130 | "phpunit/phpunit": "^7.0" 131 | }, 132 | "type": "library", 133 | "extra": { 134 | "branch-alias": { 135 | "dev-master": "1.2.x-dev" 136 | } 137 | }, 138 | "autoload": { 139 | "psr-4": { 140 | "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" 141 | } 142 | }, 143 | "notification-url": "https://packagist.org/downloads/", 144 | "license": [ 145 | "MIT" 146 | ], 147 | "authors": [ 148 | { 149 | "name": "Marco Pivetta", 150 | "email": "ocramius@gmail.com", 151 | "homepage": "http://ocramius.github.com/" 152 | } 153 | ], 154 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", 155 | "homepage": "https://www.doctrine-project.org/projects/instantiator.html", 156 | "keywords": [ 157 | "constructor", 158 | "instantiate" 159 | ], 160 | "time": "2019-10-21T16:45:58+00:00" 161 | }, 162 | { 163 | "name": "league/csv", 164 | "version": "9.4.1", 165 | "source": { 166 | "type": "git", 167 | "url": "https://github.com/thephpleague/csv.git", 168 | "reference": "bf83acc23a7d60978fce36a384f97bf9d7d2ea0c" 169 | }, 170 | "dist": { 171 | "type": "zip", 172 | "url": "https://api.github.com/repos/thephpleague/csv/zipball/bf83acc23a7d60978fce36a384f97bf9d7d2ea0c", 173 | "reference": "bf83acc23a7d60978fce36a384f97bf9d7d2ea0c", 174 | "shasum": "" 175 | }, 176 | "require": { 177 | "ext-dom": "*", 178 | "ext-json": "*", 179 | "ext-mbstring": "*", 180 | "php": ">=7.0.10" 181 | }, 182 | "require-dev": { 183 | "ext-curl": "*", 184 | "friendsofphp/php-cs-fixer": "^2.12", 185 | "phpstan/phpstan": "^0.9.2", 186 | "phpstan/phpstan-phpunit": "^0.9.4", 187 | "phpstan/phpstan-strict-rules": "^0.9.0", 188 | "phpunit/phpunit": "^6.0" 189 | }, 190 | "suggest": { 191 | "ext-iconv": "Needed to ease transcoding CSV using iconv stream filters" 192 | }, 193 | "type": "library", 194 | "extra": { 195 | "branch-alias": { 196 | "dev-master": "9.x-dev" 197 | } 198 | }, 199 | "autoload": { 200 | "psr-4": { 201 | "League\\Csv\\": "src" 202 | }, 203 | "files": [ 204 | "src/functions_include.php" 205 | ] 206 | }, 207 | "notification-url": "https://packagist.org/downloads/", 208 | "license": [ 209 | "MIT" 210 | ], 211 | "authors": [ 212 | { 213 | "name": "Ignace Nyamagana Butera", 214 | "email": "nyamsprod@gmail.com", 215 | "homepage": "https://github.com/nyamsprod/", 216 | "role": "Developer" 217 | } 218 | ], 219 | "description": "Csv data manipulation made easy in PHP", 220 | "homepage": "http://csv.thephpleague.com", 221 | "keywords": [ 222 | "csv", 223 | "export", 224 | "filter", 225 | "import", 226 | "read", 227 | "write" 228 | ], 229 | "time": "2019-10-17T06:05:32+00:00" 230 | }, 231 | { 232 | "name": "myclabs/deep-copy", 233 | "version": "1.9.3", 234 | "source": { 235 | "type": "git", 236 | "url": "https://github.com/myclabs/DeepCopy.git", 237 | "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea" 238 | }, 239 | "dist": { 240 | "type": "zip", 241 | "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/007c053ae6f31bba39dfa19a7726f56e9763bbea", 242 | "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea", 243 | "shasum": "" 244 | }, 245 | "require": { 246 | "php": "^7.1" 247 | }, 248 | "replace": { 249 | "myclabs/deep-copy": "self.version" 250 | }, 251 | "require-dev": { 252 | "doctrine/collections": "^1.0", 253 | "doctrine/common": "^2.6", 254 | "phpunit/phpunit": "^7.1" 255 | }, 256 | "type": "library", 257 | "autoload": { 258 | "psr-4": { 259 | "DeepCopy\\": "src/DeepCopy/" 260 | }, 261 | "files": [ 262 | "src/DeepCopy/deep_copy.php" 263 | ] 264 | }, 265 | "notification-url": "https://packagist.org/downloads/", 266 | "license": [ 267 | "MIT" 268 | ], 269 | "description": "Create deep copies (clones) of your objects", 270 | "keywords": [ 271 | "clone", 272 | "copy", 273 | "duplicate", 274 | "object", 275 | "object graph" 276 | ], 277 | "time": "2019-08-09T12:45:53+00:00" 278 | }, 279 | { 280 | "name": "phar-io/manifest", 281 | "version": "1.0.3", 282 | "source": { 283 | "type": "git", 284 | "url": "https://github.com/phar-io/manifest.git", 285 | "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4" 286 | }, 287 | "dist": { 288 | "type": "zip", 289 | "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", 290 | "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", 291 | "shasum": "" 292 | }, 293 | "require": { 294 | "ext-dom": "*", 295 | "ext-phar": "*", 296 | "phar-io/version": "^2.0", 297 | "php": "^5.6 || ^7.0" 298 | }, 299 | "type": "library", 300 | "extra": { 301 | "branch-alias": { 302 | "dev-master": "1.0.x-dev" 303 | } 304 | }, 305 | "autoload": { 306 | "classmap": [ 307 | "src/" 308 | ] 309 | }, 310 | "notification-url": "https://packagist.org/downloads/", 311 | "license": [ 312 | "BSD-3-Clause" 313 | ], 314 | "authors": [ 315 | { 316 | "name": "Arne Blankerts", 317 | "email": "arne@blankerts.de", 318 | "role": "Developer" 319 | }, 320 | { 321 | "name": "Sebastian Heuer", 322 | "email": "sebastian@phpeople.de", 323 | "role": "Developer" 324 | }, 325 | { 326 | "name": "Sebastian Bergmann", 327 | "email": "sebastian@phpunit.de", 328 | "role": "Developer" 329 | } 330 | ], 331 | "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", 332 | "time": "2018-07-08T19:23:20+00:00" 333 | }, 334 | { 335 | "name": "phar-io/version", 336 | "version": "2.0.1", 337 | "source": { 338 | "type": "git", 339 | "url": "https://github.com/phar-io/version.git", 340 | "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6" 341 | }, 342 | "dist": { 343 | "type": "zip", 344 | "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6", 345 | "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6", 346 | "shasum": "" 347 | }, 348 | "require": { 349 | "php": "^5.6 || ^7.0" 350 | }, 351 | "type": "library", 352 | "autoload": { 353 | "classmap": [ 354 | "src/" 355 | ] 356 | }, 357 | "notification-url": "https://packagist.org/downloads/", 358 | "license": [ 359 | "BSD-3-Clause" 360 | ], 361 | "authors": [ 362 | { 363 | "name": "Arne Blankerts", 364 | "email": "arne@blankerts.de", 365 | "role": "Developer" 366 | }, 367 | { 368 | "name": "Sebastian Heuer", 369 | "email": "sebastian@phpeople.de", 370 | "role": "Developer" 371 | }, 372 | { 373 | "name": "Sebastian Bergmann", 374 | "email": "sebastian@phpunit.de", 375 | "role": "Developer" 376 | } 377 | ], 378 | "description": "Library for handling version information and constraints", 379 | "time": "2018-07-08T19:19:57+00:00" 380 | }, 381 | { 382 | "name": "phpdocumentor/reflection-common", 383 | "version": "2.0.0", 384 | "source": { 385 | "type": "git", 386 | "url": "https://github.com/phpDocumentor/ReflectionCommon.git", 387 | "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a" 388 | }, 389 | "dist": { 390 | "type": "zip", 391 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/63a995caa1ca9e5590304cd845c15ad6d482a62a", 392 | "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a", 393 | "shasum": "" 394 | }, 395 | "require": { 396 | "php": ">=7.1" 397 | }, 398 | "require-dev": { 399 | "phpunit/phpunit": "~6" 400 | }, 401 | "type": "library", 402 | "extra": { 403 | "branch-alias": { 404 | "dev-master": "2.x-dev" 405 | } 406 | }, 407 | "autoload": { 408 | "psr-4": { 409 | "phpDocumentor\\Reflection\\": "src/" 410 | } 411 | }, 412 | "notification-url": "https://packagist.org/downloads/", 413 | "license": [ 414 | "MIT" 415 | ], 416 | "authors": [ 417 | { 418 | "name": "Jaap van Otterdijk", 419 | "email": "opensource@ijaap.nl" 420 | } 421 | ], 422 | "description": "Common reflection classes used by phpdocumentor to reflect the code structure", 423 | "homepage": "http://www.phpdoc.org", 424 | "keywords": [ 425 | "FQSEN", 426 | "phpDocumentor", 427 | "phpdoc", 428 | "reflection", 429 | "static analysis" 430 | ], 431 | "time": "2018-08-07T13:53:10+00:00" 432 | }, 433 | { 434 | "name": "phpdocumentor/reflection-docblock", 435 | "version": "4.3.2", 436 | "source": { 437 | "type": "git", 438 | "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", 439 | "reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e" 440 | }, 441 | "dist": { 442 | "type": "zip", 443 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/b83ff7cfcfee7827e1e78b637a5904fe6a96698e", 444 | "reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e", 445 | "shasum": "" 446 | }, 447 | "require": { 448 | "php": "^7.0", 449 | "phpdocumentor/reflection-common": "^1.0.0 || ^2.0.0", 450 | "phpdocumentor/type-resolver": "~0.4 || ^1.0.0", 451 | "webmozart/assert": "^1.0" 452 | }, 453 | "require-dev": { 454 | "doctrine/instantiator": "^1.0.5", 455 | "mockery/mockery": "^1.0", 456 | "phpunit/phpunit": "^6.4" 457 | }, 458 | "type": "library", 459 | "extra": { 460 | "branch-alias": { 461 | "dev-master": "4.x-dev" 462 | } 463 | }, 464 | "autoload": { 465 | "psr-4": { 466 | "phpDocumentor\\Reflection\\": [ 467 | "src/" 468 | ] 469 | } 470 | }, 471 | "notification-url": "https://packagist.org/downloads/", 472 | "license": [ 473 | "MIT" 474 | ], 475 | "authors": [ 476 | { 477 | "name": "Mike van Riel", 478 | "email": "me@mikevanriel.com" 479 | } 480 | ], 481 | "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", 482 | "time": "2019-09-12T14:27:41+00:00" 483 | }, 484 | { 485 | "name": "phpdocumentor/type-resolver", 486 | "version": "1.0.1", 487 | "source": { 488 | "type": "git", 489 | "url": "https://github.com/phpDocumentor/TypeResolver.git", 490 | "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9" 491 | }, 492 | "dist": { 493 | "type": "zip", 494 | "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", 495 | "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", 496 | "shasum": "" 497 | }, 498 | "require": { 499 | "php": "^7.1", 500 | "phpdocumentor/reflection-common": "^2.0" 501 | }, 502 | "require-dev": { 503 | "ext-tokenizer": "^7.1", 504 | "mockery/mockery": "~1", 505 | "phpunit/phpunit": "^7.0" 506 | }, 507 | "type": "library", 508 | "extra": { 509 | "branch-alias": { 510 | "dev-master": "1.x-dev" 511 | } 512 | }, 513 | "autoload": { 514 | "psr-4": { 515 | "phpDocumentor\\Reflection\\": "src" 516 | } 517 | }, 518 | "notification-url": "https://packagist.org/downloads/", 519 | "license": [ 520 | "MIT" 521 | ], 522 | "authors": [ 523 | { 524 | "name": "Mike van Riel", 525 | "email": "me@mikevanriel.com" 526 | } 527 | ], 528 | "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", 529 | "time": "2019-08-22T18:11:29+00:00" 530 | }, 531 | { 532 | "name": "phpspec/prophecy", 533 | "version": "1.9.0", 534 | "source": { 535 | "type": "git", 536 | "url": "https://github.com/phpspec/prophecy.git", 537 | "reference": "f6811d96d97bdf400077a0cc100ae56aa32b9203" 538 | }, 539 | "dist": { 540 | "type": "zip", 541 | "url": "https://api.github.com/repos/phpspec/prophecy/zipball/f6811d96d97bdf400077a0cc100ae56aa32b9203", 542 | "reference": "f6811d96d97bdf400077a0cc100ae56aa32b9203", 543 | "shasum": "" 544 | }, 545 | "require": { 546 | "doctrine/instantiator": "^1.0.2", 547 | "php": "^5.3|^7.0", 548 | "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", 549 | "sebastian/comparator": "^1.1|^2.0|^3.0", 550 | "sebastian/recursion-context": "^1.0|^2.0|^3.0" 551 | }, 552 | "require-dev": { 553 | "phpspec/phpspec": "^2.5|^3.2", 554 | "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" 555 | }, 556 | "type": "library", 557 | "extra": { 558 | "branch-alias": { 559 | "dev-master": "1.8.x-dev" 560 | } 561 | }, 562 | "autoload": { 563 | "psr-4": { 564 | "Prophecy\\": "src/Prophecy" 565 | } 566 | }, 567 | "notification-url": "https://packagist.org/downloads/", 568 | "license": [ 569 | "MIT" 570 | ], 571 | "authors": [ 572 | { 573 | "name": "Konstantin Kudryashov", 574 | "email": "ever.zet@gmail.com", 575 | "homepage": "http://everzet.com" 576 | }, 577 | { 578 | "name": "Marcello Duarte", 579 | "email": "marcello.duarte@gmail.com" 580 | } 581 | ], 582 | "description": "Highly opinionated mocking framework for PHP 5.3+", 583 | "homepage": "https://github.com/phpspec/prophecy", 584 | "keywords": [ 585 | "Double", 586 | "Dummy", 587 | "fake", 588 | "mock", 589 | "spy", 590 | "stub" 591 | ], 592 | "time": "2019-10-03T11:07:50+00:00" 593 | }, 594 | { 595 | "name": "phpunit/php-code-coverage", 596 | "version": "7.0.10", 597 | "source": { 598 | "type": "git", 599 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", 600 | "reference": "f1884187926fbb755a9aaf0b3836ad3165b478bf" 601 | }, 602 | "dist": { 603 | "type": "zip", 604 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f1884187926fbb755a9aaf0b3836ad3165b478bf", 605 | "reference": "f1884187926fbb755a9aaf0b3836ad3165b478bf", 606 | "shasum": "" 607 | }, 608 | "require": { 609 | "ext-dom": "*", 610 | "ext-xmlwriter": "*", 611 | "php": "^7.2", 612 | "phpunit/php-file-iterator": "^2.0.2", 613 | "phpunit/php-text-template": "^1.2.1", 614 | "phpunit/php-token-stream": "^3.1.1", 615 | "sebastian/code-unit-reverse-lookup": "^1.0.1", 616 | "sebastian/environment": "^4.2.2", 617 | "sebastian/version": "^2.0.1", 618 | "theseer/tokenizer": "^1.1.3" 619 | }, 620 | "require-dev": { 621 | "phpunit/phpunit": "^8.2.2" 622 | }, 623 | "suggest": { 624 | "ext-xdebug": "^2.7.2" 625 | }, 626 | "type": "library", 627 | "extra": { 628 | "branch-alias": { 629 | "dev-master": "7.0-dev" 630 | } 631 | }, 632 | "autoload": { 633 | "classmap": [ 634 | "src/" 635 | ] 636 | }, 637 | "notification-url": "https://packagist.org/downloads/", 638 | "license": [ 639 | "BSD-3-Clause" 640 | ], 641 | "authors": [ 642 | { 643 | "name": "Sebastian Bergmann", 644 | "email": "sebastian@phpunit.de", 645 | "role": "lead" 646 | } 647 | ], 648 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", 649 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage", 650 | "keywords": [ 651 | "coverage", 652 | "testing", 653 | "xunit" 654 | ], 655 | "time": "2019-11-20T13:55:58+00:00" 656 | }, 657 | { 658 | "name": "phpunit/php-file-iterator", 659 | "version": "2.0.2", 660 | "source": { 661 | "type": "git", 662 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", 663 | "reference": "050bedf145a257b1ff02746c31894800e5122946" 664 | }, 665 | "dist": { 666 | "type": "zip", 667 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/050bedf145a257b1ff02746c31894800e5122946", 668 | "reference": "050bedf145a257b1ff02746c31894800e5122946", 669 | "shasum": "" 670 | }, 671 | "require": { 672 | "php": "^7.1" 673 | }, 674 | "require-dev": { 675 | "phpunit/phpunit": "^7.1" 676 | }, 677 | "type": "library", 678 | "extra": { 679 | "branch-alias": { 680 | "dev-master": "2.0.x-dev" 681 | } 682 | }, 683 | "autoload": { 684 | "classmap": [ 685 | "src/" 686 | ] 687 | }, 688 | "notification-url": "https://packagist.org/downloads/", 689 | "license": [ 690 | "BSD-3-Clause" 691 | ], 692 | "authors": [ 693 | { 694 | "name": "Sebastian Bergmann", 695 | "email": "sebastian@phpunit.de", 696 | "role": "lead" 697 | } 698 | ], 699 | "description": "FilterIterator implementation that filters files based on a list of suffixes.", 700 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", 701 | "keywords": [ 702 | "filesystem", 703 | "iterator" 704 | ], 705 | "time": "2018-09-13T20:33:42+00:00" 706 | }, 707 | { 708 | "name": "phpunit/php-text-template", 709 | "version": "1.2.1", 710 | "source": { 711 | "type": "git", 712 | "url": "https://github.com/sebastianbergmann/php-text-template.git", 713 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" 714 | }, 715 | "dist": { 716 | "type": "zip", 717 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", 718 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", 719 | "shasum": "" 720 | }, 721 | "require": { 722 | "php": ">=5.3.3" 723 | }, 724 | "type": "library", 725 | "autoload": { 726 | "classmap": [ 727 | "src/" 728 | ] 729 | }, 730 | "notification-url": "https://packagist.org/downloads/", 731 | "license": [ 732 | "BSD-3-Clause" 733 | ], 734 | "authors": [ 735 | { 736 | "name": "Sebastian Bergmann", 737 | "email": "sebastian@phpunit.de", 738 | "role": "lead" 739 | } 740 | ], 741 | "description": "Simple template engine.", 742 | "homepage": "https://github.com/sebastianbergmann/php-text-template/", 743 | "keywords": [ 744 | "template" 745 | ], 746 | "time": "2015-06-21T13:50:34+00:00" 747 | }, 748 | { 749 | "name": "phpunit/php-timer", 750 | "version": "2.1.2", 751 | "source": { 752 | "type": "git", 753 | "url": "https://github.com/sebastianbergmann/php-timer.git", 754 | "reference": "1038454804406b0b5f5f520358e78c1c2f71501e" 755 | }, 756 | "dist": { 757 | "type": "zip", 758 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/1038454804406b0b5f5f520358e78c1c2f71501e", 759 | "reference": "1038454804406b0b5f5f520358e78c1c2f71501e", 760 | "shasum": "" 761 | }, 762 | "require": { 763 | "php": "^7.1" 764 | }, 765 | "require-dev": { 766 | "phpunit/phpunit": "^7.0" 767 | }, 768 | "type": "library", 769 | "extra": { 770 | "branch-alias": { 771 | "dev-master": "2.1-dev" 772 | } 773 | }, 774 | "autoload": { 775 | "classmap": [ 776 | "src/" 777 | ] 778 | }, 779 | "notification-url": "https://packagist.org/downloads/", 780 | "license": [ 781 | "BSD-3-Clause" 782 | ], 783 | "authors": [ 784 | { 785 | "name": "Sebastian Bergmann", 786 | "email": "sebastian@phpunit.de", 787 | "role": "lead" 788 | } 789 | ], 790 | "description": "Utility class for timing", 791 | "homepage": "https://github.com/sebastianbergmann/php-timer/", 792 | "keywords": [ 793 | "timer" 794 | ], 795 | "time": "2019-06-07T04:22:29+00:00" 796 | }, 797 | { 798 | "name": "phpunit/php-token-stream", 799 | "version": "3.1.1", 800 | "source": { 801 | "type": "git", 802 | "url": "https://github.com/sebastianbergmann/php-token-stream.git", 803 | "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff" 804 | }, 805 | "dist": { 806 | "type": "zip", 807 | "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/995192df77f63a59e47f025390d2d1fdf8f425ff", 808 | "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff", 809 | "shasum": "" 810 | }, 811 | "require": { 812 | "ext-tokenizer": "*", 813 | "php": "^7.1" 814 | }, 815 | "require-dev": { 816 | "phpunit/phpunit": "^7.0" 817 | }, 818 | "type": "library", 819 | "extra": { 820 | "branch-alias": { 821 | "dev-master": "3.1-dev" 822 | } 823 | }, 824 | "autoload": { 825 | "classmap": [ 826 | "src/" 827 | ] 828 | }, 829 | "notification-url": "https://packagist.org/downloads/", 830 | "license": [ 831 | "BSD-3-Clause" 832 | ], 833 | "authors": [ 834 | { 835 | "name": "Sebastian Bergmann", 836 | "email": "sebastian@phpunit.de" 837 | } 838 | ], 839 | "description": "Wrapper around PHP's tokenizer extension.", 840 | "homepage": "https://github.com/sebastianbergmann/php-token-stream/", 841 | "keywords": [ 842 | "tokenizer" 843 | ], 844 | "time": "2019-09-17T06:23:10+00:00" 845 | }, 846 | { 847 | "name": "phpunit/phpunit", 848 | "version": "8.5.0", 849 | "source": { 850 | "type": "git", 851 | "url": "https://github.com/sebastianbergmann/phpunit.git", 852 | "reference": "3ee1c1fd6fc264480c25b6fb8285edefe1702dab" 853 | }, 854 | "dist": { 855 | "type": "zip", 856 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3ee1c1fd6fc264480c25b6fb8285edefe1702dab", 857 | "reference": "3ee1c1fd6fc264480c25b6fb8285edefe1702dab", 858 | "shasum": "" 859 | }, 860 | "require": { 861 | "doctrine/instantiator": "^1.2.0", 862 | "ext-dom": "*", 863 | "ext-json": "*", 864 | "ext-libxml": "*", 865 | "ext-mbstring": "*", 866 | "ext-xml": "*", 867 | "ext-xmlwriter": "*", 868 | "myclabs/deep-copy": "^1.9.1", 869 | "phar-io/manifest": "^1.0.3", 870 | "phar-io/version": "^2.0.1", 871 | "php": "^7.2", 872 | "phpspec/prophecy": "^1.8.1", 873 | "phpunit/php-code-coverage": "^7.0.7", 874 | "phpunit/php-file-iterator": "^2.0.2", 875 | "phpunit/php-text-template": "^1.2.1", 876 | "phpunit/php-timer": "^2.1.2", 877 | "sebastian/comparator": "^3.0.2", 878 | "sebastian/diff": "^3.0.2", 879 | "sebastian/environment": "^4.2.2", 880 | "sebastian/exporter": "^3.1.1", 881 | "sebastian/global-state": "^3.0.0", 882 | "sebastian/object-enumerator": "^3.0.3", 883 | "sebastian/resource-operations": "^2.0.1", 884 | "sebastian/type": "^1.1.3", 885 | "sebastian/version": "^2.0.1" 886 | }, 887 | "require-dev": { 888 | "ext-pdo": "*" 889 | }, 890 | "suggest": { 891 | "ext-soap": "*", 892 | "ext-xdebug": "*", 893 | "phpunit/php-invoker": "^2.0.0" 894 | }, 895 | "bin": [ 896 | "phpunit" 897 | ], 898 | "type": "library", 899 | "extra": { 900 | "branch-alias": { 901 | "dev-master": "8.5-dev" 902 | } 903 | }, 904 | "autoload": { 905 | "classmap": [ 906 | "src/" 907 | ] 908 | }, 909 | "notification-url": "https://packagist.org/downloads/", 910 | "license": [ 911 | "BSD-3-Clause" 912 | ], 913 | "authors": [ 914 | { 915 | "name": "Sebastian Bergmann", 916 | "email": "sebastian@phpunit.de", 917 | "role": "lead" 918 | } 919 | ], 920 | "description": "The PHP Unit Testing framework.", 921 | "homepage": "https://phpunit.de/", 922 | "keywords": [ 923 | "phpunit", 924 | "testing", 925 | "xunit" 926 | ], 927 | "time": "2019-12-06T05:41:38+00:00" 928 | }, 929 | { 930 | "name": "sebastian/code-unit-reverse-lookup", 931 | "version": "1.0.1", 932 | "source": { 933 | "type": "git", 934 | "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", 935 | "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" 936 | }, 937 | "dist": { 938 | "type": "zip", 939 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", 940 | "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", 941 | "shasum": "" 942 | }, 943 | "require": { 944 | "php": "^5.6 || ^7.0" 945 | }, 946 | "require-dev": { 947 | "phpunit/phpunit": "^5.7 || ^6.0" 948 | }, 949 | "type": "library", 950 | "extra": { 951 | "branch-alias": { 952 | "dev-master": "1.0.x-dev" 953 | } 954 | }, 955 | "autoload": { 956 | "classmap": [ 957 | "src/" 958 | ] 959 | }, 960 | "notification-url": "https://packagist.org/downloads/", 961 | "license": [ 962 | "BSD-3-Clause" 963 | ], 964 | "authors": [ 965 | { 966 | "name": "Sebastian Bergmann", 967 | "email": "sebastian@phpunit.de" 968 | } 969 | ], 970 | "description": "Looks up which function or method a line of code belongs to", 971 | "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", 972 | "time": "2017-03-04T06:30:41+00:00" 973 | }, 974 | { 975 | "name": "sebastian/comparator", 976 | "version": "3.0.2", 977 | "source": { 978 | "type": "git", 979 | "url": "https://github.com/sebastianbergmann/comparator.git", 980 | "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da" 981 | }, 982 | "dist": { 983 | "type": "zip", 984 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/5de4fc177adf9bce8df98d8d141a7559d7ccf6da", 985 | "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da", 986 | "shasum": "" 987 | }, 988 | "require": { 989 | "php": "^7.1", 990 | "sebastian/diff": "^3.0", 991 | "sebastian/exporter": "^3.1" 992 | }, 993 | "require-dev": { 994 | "phpunit/phpunit": "^7.1" 995 | }, 996 | "type": "library", 997 | "extra": { 998 | "branch-alias": { 999 | "dev-master": "3.0-dev" 1000 | } 1001 | }, 1002 | "autoload": { 1003 | "classmap": [ 1004 | "src/" 1005 | ] 1006 | }, 1007 | "notification-url": "https://packagist.org/downloads/", 1008 | "license": [ 1009 | "BSD-3-Clause" 1010 | ], 1011 | "authors": [ 1012 | { 1013 | "name": "Jeff Welch", 1014 | "email": "whatthejeff@gmail.com" 1015 | }, 1016 | { 1017 | "name": "Volker Dusch", 1018 | "email": "github@wallbash.com" 1019 | }, 1020 | { 1021 | "name": "Bernhard Schussek", 1022 | "email": "bschussek@2bepublished.at" 1023 | }, 1024 | { 1025 | "name": "Sebastian Bergmann", 1026 | "email": "sebastian@phpunit.de" 1027 | } 1028 | ], 1029 | "description": "Provides the functionality to compare PHP values for equality", 1030 | "homepage": "https://github.com/sebastianbergmann/comparator", 1031 | "keywords": [ 1032 | "comparator", 1033 | "compare", 1034 | "equality" 1035 | ], 1036 | "time": "2018-07-12T15:12:46+00:00" 1037 | }, 1038 | { 1039 | "name": "sebastian/diff", 1040 | "version": "3.0.2", 1041 | "source": { 1042 | "type": "git", 1043 | "url": "https://github.com/sebastianbergmann/diff.git", 1044 | "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29" 1045 | }, 1046 | "dist": { 1047 | "type": "zip", 1048 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/720fcc7e9b5cf384ea68d9d930d480907a0c1a29", 1049 | "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29", 1050 | "shasum": "" 1051 | }, 1052 | "require": { 1053 | "php": "^7.1" 1054 | }, 1055 | "require-dev": { 1056 | "phpunit/phpunit": "^7.5 || ^8.0", 1057 | "symfony/process": "^2 || ^3.3 || ^4" 1058 | }, 1059 | "type": "library", 1060 | "extra": { 1061 | "branch-alias": { 1062 | "dev-master": "3.0-dev" 1063 | } 1064 | }, 1065 | "autoload": { 1066 | "classmap": [ 1067 | "src/" 1068 | ] 1069 | }, 1070 | "notification-url": "https://packagist.org/downloads/", 1071 | "license": [ 1072 | "BSD-3-Clause" 1073 | ], 1074 | "authors": [ 1075 | { 1076 | "name": "Kore Nordmann", 1077 | "email": "mail@kore-nordmann.de" 1078 | }, 1079 | { 1080 | "name": "Sebastian Bergmann", 1081 | "email": "sebastian@phpunit.de" 1082 | } 1083 | ], 1084 | "description": "Diff implementation", 1085 | "homepage": "https://github.com/sebastianbergmann/diff", 1086 | "keywords": [ 1087 | "diff", 1088 | "udiff", 1089 | "unidiff", 1090 | "unified diff" 1091 | ], 1092 | "time": "2019-02-04T06:01:07+00:00" 1093 | }, 1094 | { 1095 | "name": "sebastian/environment", 1096 | "version": "4.2.3", 1097 | "source": { 1098 | "type": "git", 1099 | "url": "https://github.com/sebastianbergmann/environment.git", 1100 | "reference": "464c90d7bdf5ad4e8a6aea15c091fec0603d4368" 1101 | }, 1102 | "dist": { 1103 | "type": "zip", 1104 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/464c90d7bdf5ad4e8a6aea15c091fec0603d4368", 1105 | "reference": "464c90d7bdf5ad4e8a6aea15c091fec0603d4368", 1106 | "shasum": "" 1107 | }, 1108 | "require": { 1109 | "php": "^7.1" 1110 | }, 1111 | "require-dev": { 1112 | "phpunit/phpunit": "^7.5" 1113 | }, 1114 | "suggest": { 1115 | "ext-posix": "*" 1116 | }, 1117 | "type": "library", 1118 | "extra": { 1119 | "branch-alias": { 1120 | "dev-master": "4.2-dev" 1121 | } 1122 | }, 1123 | "autoload": { 1124 | "classmap": [ 1125 | "src/" 1126 | ] 1127 | }, 1128 | "notification-url": "https://packagist.org/downloads/", 1129 | "license": [ 1130 | "BSD-3-Clause" 1131 | ], 1132 | "authors": [ 1133 | { 1134 | "name": "Sebastian Bergmann", 1135 | "email": "sebastian@phpunit.de" 1136 | } 1137 | ], 1138 | "description": "Provides functionality to handle HHVM/PHP environments", 1139 | "homepage": "http://www.github.com/sebastianbergmann/environment", 1140 | "keywords": [ 1141 | "Xdebug", 1142 | "environment", 1143 | "hhvm" 1144 | ], 1145 | "time": "2019-11-20T08:46:58+00:00" 1146 | }, 1147 | { 1148 | "name": "sebastian/exporter", 1149 | "version": "3.1.2", 1150 | "source": { 1151 | "type": "git", 1152 | "url": "https://github.com/sebastianbergmann/exporter.git", 1153 | "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e" 1154 | }, 1155 | "dist": { 1156 | "type": "zip", 1157 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/68609e1261d215ea5b21b7987539cbfbe156ec3e", 1158 | "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e", 1159 | "shasum": "" 1160 | }, 1161 | "require": { 1162 | "php": "^7.0", 1163 | "sebastian/recursion-context": "^3.0" 1164 | }, 1165 | "require-dev": { 1166 | "ext-mbstring": "*", 1167 | "phpunit/phpunit": "^6.0" 1168 | }, 1169 | "type": "library", 1170 | "extra": { 1171 | "branch-alias": { 1172 | "dev-master": "3.1.x-dev" 1173 | } 1174 | }, 1175 | "autoload": { 1176 | "classmap": [ 1177 | "src/" 1178 | ] 1179 | }, 1180 | "notification-url": "https://packagist.org/downloads/", 1181 | "license": [ 1182 | "BSD-3-Clause" 1183 | ], 1184 | "authors": [ 1185 | { 1186 | "name": "Sebastian Bergmann", 1187 | "email": "sebastian@phpunit.de" 1188 | }, 1189 | { 1190 | "name": "Jeff Welch", 1191 | "email": "whatthejeff@gmail.com" 1192 | }, 1193 | { 1194 | "name": "Volker Dusch", 1195 | "email": "github@wallbash.com" 1196 | }, 1197 | { 1198 | "name": "Adam Harvey", 1199 | "email": "aharvey@php.net" 1200 | }, 1201 | { 1202 | "name": "Bernhard Schussek", 1203 | "email": "bschussek@gmail.com" 1204 | } 1205 | ], 1206 | "description": "Provides the functionality to export PHP variables for visualization", 1207 | "homepage": "http://www.github.com/sebastianbergmann/exporter", 1208 | "keywords": [ 1209 | "export", 1210 | "exporter" 1211 | ], 1212 | "time": "2019-09-14T09:02:43+00:00" 1213 | }, 1214 | { 1215 | "name": "sebastian/global-state", 1216 | "version": "3.0.0", 1217 | "source": { 1218 | "type": "git", 1219 | "url": "https://github.com/sebastianbergmann/global-state.git", 1220 | "reference": "edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4" 1221 | }, 1222 | "dist": { 1223 | "type": "zip", 1224 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4", 1225 | "reference": "edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4", 1226 | "shasum": "" 1227 | }, 1228 | "require": { 1229 | "php": "^7.2", 1230 | "sebastian/object-reflector": "^1.1.1", 1231 | "sebastian/recursion-context": "^3.0" 1232 | }, 1233 | "require-dev": { 1234 | "ext-dom": "*", 1235 | "phpunit/phpunit": "^8.0" 1236 | }, 1237 | "suggest": { 1238 | "ext-uopz": "*" 1239 | }, 1240 | "type": "library", 1241 | "extra": { 1242 | "branch-alias": { 1243 | "dev-master": "3.0-dev" 1244 | } 1245 | }, 1246 | "autoload": { 1247 | "classmap": [ 1248 | "src/" 1249 | ] 1250 | }, 1251 | "notification-url": "https://packagist.org/downloads/", 1252 | "license": [ 1253 | "BSD-3-Clause" 1254 | ], 1255 | "authors": [ 1256 | { 1257 | "name": "Sebastian Bergmann", 1258 | "email": "sebastian@phpunit.de" 1259 | } 1260 | ], 1261 | "description": "Snapshotting of global state", 1262 | "homepage": "http://www.github.com/sebastianbergmann/global-state", 1263 | "keywords": [ 1264 | "global state" 1265 | ], 1266 | "time": "2019-02-01T05:30:01+00:00" 1267 | }, 1268 | { 1269 | "name": "sebastian/object-enumerator", 1270 | "version": "3.0.3", 1271 | "source": { 1272 | "type": "git", 1273 | "url": "https://github.com/sebastianbergmann/object-enumerator.git", 1274 | "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" 1275 | }, 1276 | "dist": { 1277 | "type": "zip", 1278 | "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", 1279 | "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", 1280 | "shasum": "" 1281 | }, 1282 | "require": { 1283 | "php": "^7.0", 1284 | "sebastian/object-reflector": "^1.1.1", 1285 | "sebastian/recursion-context": "^3.0" 1286 | }, 1287 | "require-dev": { 1288 | "phpunit/phpunit": "^6.0" 1289 | }, 1290 | "type": "library", 1291 | "extra": { 1292 | "branch-alias": { 1293 | "dev-master": "3.0.x-dev" 1294 | } 1295 | }, 1296 | "autoload": { 1297 | "classmap": [ 1298 | "src/" 1299 | ] 1300 | }, 1301 | "notification-url": "https://packagist.org/downloads/", 1302 | "license": [ 1303 | "BSD-3-Clause" 1304 | ], 1305 | "authors": [ 1306 | { 1307 | "name": "Sebastian Bergmann", 1308 | "email": "sebastian@phpunit.de" 1309 | } 1310 | ], 1311 | "description": "Traverses array structures and object graphs to enumerate all referenced objects", 1312 | "homepage": "https://github.com/sebastianbergmann/object-enumerator/", 1313 | "time": "2017-08-03T12:35:26+00:00" 1314 | }, 1315 | { 1316 | "name": "sebastian/object-reflector", 1317 | "version": "1.1.1", 1318 | "source": { 1319 | "type": "git", 1320 | "url": "https://github.com/sebastianbergmann/object-reflector.git", 1321 | "reference": "773f97c67f28de00d397be301821b06708fca0be" 1322 | }, 1323 | "dist": { 1324 | "type": "zip", 1325 | "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", 1326 | "reference": "773f97c67f28de00d397be301821b06708fca0be", 1327 | "shasum": "" 1328 | }, 1329 | "require": { 1330 | "php": "^7.0" 1331 | }, 1332 | "require-dev": { 1333 | "phpunit/phpunit": "^6.0" 1334 | }, 1335 | "type": "library", 1336 | "extra": { 1337 | "branch-alias": { 1338 | "dev-master": "1.1-dev" 1339 | } 1340 | }, 1341 | "autoload": { 1342 | "classmap": [ 1343 | "src/" 1344 | ] 1345 | }, 1346 | "notification-url": "https://packagist.org/downloads/", 1347 | "license": [ 1348 | "BSD-3-Clause" 1349 | ], 1350 | "authors": [ 1351 | { 1352 | "name": "Sebastian Bergmann", 1353 | "email": "sebastian@phpunit.de" 1354 | } 1355 | ], 1356 | "description": "Allows reflection of object attributes, including inherited and non-public ones", 1357 | "homepage": "https://github.com/sebastianbergmann/object-reflector/", 1358 | "time": "2017-03-29T09:07:27+00:00" 1359 | }, 1360 | { 1361 | "name": "sebastian/recursion-context", 1362 | "version": "3.0.0", 1363 | "source": { 1364 | "type": "git", 1365 | "url": "https://github.com/sebastianbergmann/recursion-context.git", 1366 | "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" 1367 | }, 1368 | "dist": { 1369 | "type": "zip", 1370 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", 1371 | "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", 1372 | "shasum": "" 1373 | }, 1374 | "require": { 1375 | "php": "^7.0" 1376 | }, 1377 | "require-dev": { 1378 | "phpunit/phpunit": "^6.0" 1379 | }, 1380 | "type": "library", 1381 | "extra": { 1382 | "branch-alias": { 1383 | "dev-master": "3.0.x-dev" 1384 | } 1385 | }, 1386 | "autoload": { 1387 | "classmap": [ 1388 | "src/" 1389 | ] 1390 | }, 1391 | "notification-url": "https://packagist.org/downloads/", 1392 | "license": [ 1393 | "BSD-3-Clause" 1394 | ], 1395 | "authors": [ 1396 | { 1397 | "name": "Jeff Welch", 1398 | "email": "whatthejeff@gmail.com" 1399 | }, 1400 | { 1401 | "name": "Sebastian Bergmann", 1402 | "email": "sebastian@phpunit.de" 1403 | }, 1404 | { 1405 | "name": "Adam Harvey", 1406 | "email": "aharvey@php.net" 1407 | } 1408 | ], 1409 | "description": "Provides functionality to recursively process PHP variables", 1410 | "homepage": "http://www.github.com/sebastianbergmann/recursion-context", 1411 | "time": "2017-03-03T06:23:57+00:00" 1412 | }, 1413 | { 1414 | "name": "sebastian/resource-operations", 1415 | "version": "2.0.1", 1416 | "source": { 1417 | "type": "git", 1418 | "url": "https://github.com/sebastianbergmann/resource-operations.git", 1419 | "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9" 1420 | }, 1421 | "dist": { 1422 | "type": "zip", 1423 | "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/4d7a795d35b889bf80a0cc04e08d77cedfa917a9", 1424 | "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9", 1425 | "shasum": "" 1426 | }, 1427 | "require": { 1428 | "php": "^7.1" 1429 | }, 1430 | "type": "library", 1431 | "extra": { 1432 | "branch-alias": { 1433 | "dev-master": "2.0-dev" 1434 | } 1435 | }, 1436 | "autoload": { 1437 | "classmap": [ 1438 | "src/" 1439 | ] 1440 | }, 1441 | "notification-url": "https://packagist.org/downloads/", 1442 | "license": [ 1443 | "BSD-3-Clause" 1444 | ], 1445 | "authors": [ 1446 | { 1447 | "name": "Sebastian Bergmann", 1448 | "email": "sebastian@phpunit.de" 1449 | } 1450 | ], 1451 | "description": "Provides a list of PHP built-in functions that operate on resources", 1452 | "homepage": "https://www.github.com/sebastianbergmann/resource-operations", 1453 | "time": "2018-10-04T04:07:39+00:00" 1454 | }, 1455 | { 1456 | "name": "sebastian/type", 1457 | "version": "1.1.3", 1458 | "source": { 1459 | "type": "git", 1460 | "url": "https://github.com/sebastianbergmann/type.git", 1461 | "reference": "3aaaa15fa71d27650d62a948be022fe3b48541a3" 1462 | }, 1463 | "dist": { 1464 | "type": "zip", 1465 | "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/3aaaa15fa71d27650d62a948be022fe3b48541a3", 1466 | "reference": "3aaaa15fa71d27650d62a948be022fe3b48541a3", 1467 | "shasum": "" 1468 | }, 1469 | "require": { 1470 | "php": "^7.2" 1471 | }, 1472 | "require-dev": { 1473 | "phpunit/phpunit": "^8.2" 1474 | }, 1475 | "type": "library", 1476 | "extra": { 1477 | "branch-alias": { 1478 | "dev-master": "1.1-dev" 1479 | } 1480 | }, 1481 | "autoload": { 1482 | "classmap": [ 1483 | "src/" 1484 | ] 1485 | }, 1486 | "notification-url": "https://packagist.org/downloads/", 1487 | "license": [ 1488 | "BSD-3-Clause" 1489 | ], 1490 | "authors": [ 1491 | { 1492 | "name": "Sebastian Bergmann", 1493 | "email": "sebastian@phpunit.de", 1494 | "role": "lead" 1495 | } 1496 | ], 1497 | "description": "Collection of value objects that represent the types of the PHP type system", 1498 | "homepage": "https://github.com/sebastianbergmann/type", 1499 | "time": "2019-07-02T08:10:15+00:00" 1500 | }, 1501 | { 1502 | "name": "sebastian/version", 1503 | "version": "2.0.1", 1504 | "source": { 1505 | "type": "git", 1506 | "url": "https://github.com/sebastianbergmann/version.git", 1507 | "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" 1508 | }, 1509 | "dist": { 1510 | "type": "zip", 1511 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", 1512 | "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", 1513 | "shasum": "" 1514 | }, 1515 | "require": { 1516 | "php": ">=5.6" 1517 | }, 1518 | "type": "library", 1519 | "extra": { 1520 | "branch-alias": { 1521 | "dev-master": "2.0.x-dev" 1522 | } 1523 | }, 1524 | "autoload": { 1525 | "classmap": [ 1526 | "src/" 1527 | ] 1528 | }, 1529 | "notification-url": "https://packagist.org/downloads/", 1530 | "license": [ 1531 | "BSD-3-Clause" 1532 | ], 1533 | "authors": [ 1534 | { 1535 | "name": "Sebastian Bergmann", 1536 | "email": "sebastian@phpunit.de", 1537 | "role": "lead" 1538 | } 1539 | ], 1540 | "description": "Library that helps with managing the version number of Git-hosted PHP projects", 1541 | "homepage": "https://github.com/sebastianbergmann/version", 1542 | "time": "2016-10-03T07:35:21+00:00" 1543 | }, 1544 | { 1545 | "name": "symfony/polyfill-ctype", 1546 | "version": "v1.13.1", 1547 | "source": { 1548 | "type": "git", 1549 | "url": "https://github.com/symfony/polyfill-ctype.git", 1550 | "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3" 1551 | }, 1552 | "dist": { 1553 | "type": "zip", 1554 | "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/f8f0b461be3385e56d6de3dbb5a0df24c0c275e3", 1555 | "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3", 1556 | "shasum": "" 1557 | }, 1558 | "require": { 1559 | "php": ">=5.3.3" 1560 | }, 1561 | "suggest": { 1562 | "ext-ctype": "For best performance" 1563 | }, 1564 | "type": "library", 1565 | "extra": { 1566 | "branch-alias": { 1567 | "dev-master": "1.13-dev" 1568 | } 1569 | }, 1570 | "autoload": { 1571 | "psr-4": { 1572 | "Symfony\\Polyfill\\Ctype\\": "" 1573 | }, 1574 | "files": [ 1575 | "bootstrap.php" 1576 | ] 1577 | }, 1578 | "notification-url": "https://packagist.org/downloads/", 1579 | "license": [ 1580 | "MIT" 1581 | ], 1582 | "authors": [ 1583 | { 1584 | "name": "Gert de Pagter", 1585 | "email": "BackEndTea@gmail.com" 1586 | }, 1587 | { 1588 | "name": "Symfony Community", 1589 | "homepage": "https://symfony.com/contributors" 1590 | } 1591 | ], 1592 | "description": "Symfony polyfill for ctype functions", 1593 | "homepage": "https://symfony.com", 1594 | "keywords": [ 1595 | "compatibility", 1596 | "ctype", 1597 | "polyfill", 1598 | "portable" 1599 | ], 1600 | "time": "2019-11-27T13:56:44+00:00" 1601 | }, 1602 | { 1603 | "name": "theseer/tokenizer", 1604 | "version": "1.1.3", 1605 | "source": { 1606 | "type": "git", 1607 | "url": "https://github.com/theseer/tokenizer.git", 1608 | "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9" 1609 | }, 1610 | "dist": { 1611 | "type": "zip", 1612 | "url": "https://api.github.com/repos/theseer/tokenizer/zipball/11336f6f84e16a720dae9d8e6ed5019efa85a0f9", 1613 | "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9", 1614 | "shasum": "" 1615 | }, 1616 | "require": { 1617 | "ext-dom": "*", 1618 | "ext-tokenizer": "*", 1619 | "ext-xmlwriter": "*", 1620 | "php": "^7.0" 1621 | }, 1622 | "type": "library", 1623 | "autoload": { 1624 | "classmap": [ 1625 | "src/" 1626 | ] 1627 | }, 1628 | "notification-url": "https://packagist.org/downloads/", 1629 | "license": [ 1630 | "BSD-3-Clause" 1631 | ], 1632 | "authors": [ 1633 | { 1634 | "name": "Arne Blankerts", 1635 | "email": "arne@blankerts.de", 1636 | "role": "Developer" 1637 | } 1638 | ], 1639 | "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", 1640 | "time": "2019-06-13T22:48:21+00:00" 1641 | }, 1642 | { 1643 | "name": "webmozart/assert", 1644 | "version": "1.6.0", 1645 | "source": { 1646 | "type": "git", 1647 | "url": "https://github.com/webmozart/assert.git", 1648 | "reference": "573381c0a64f155a0d9a23f4b0c797194805b925" 1649 | }, 1650 | "dist": { 1651 | "type": "zip", 1652 | "url": "https://api.github.com/repos/webmozart/assert/zipball/573381c0a64f155a0d9a23f4b0c797194805b925", 1653 | "reference": "573381c0a64f155a0d9a23f4b0c797194805b925", 1654 | "shasum": "" 1655 | }, 1656 | "require": { 1657 | "php": "^5.3.3 || ^7.0", 1658 | "symfony/polyfill-ctype": "^1.8" 1659 | }, 1660 | "conflict": { 1661 | "vimeo/psalm": "<3.6.0" 1662 | }, 1663 | "require-dev": { 1664 | "phpunit/phpunit": "^4.8.36 || ^7.5.13" 1665 | }, 1666 | "type": "library", 1667 | "autoload": { 1668 | "psr-4": { 1669 | "Webmozart\\Assert\\": "src/" 1670 | } 1671 | }, 1672 | "notification-url": "https://packagist.org/downloads/", 1673 | "license": [ 1674 | "MIT" 1675 | ], 1676 | "authors": [ 1677 | { 1678 | "name": "Bernhard Schussek", 1679 | "email": "bschussek@gmail.com" 1680 | } 1681 | ], 1682 | "description": "Assertions to validate method input/output with nice error messages.", 1683 | "keywords": [ 1684 | "assert", 1685 | "check", 1686 | "validate" 1687 | ], 1688 | "time": "2019-11-24T13:36:37+00:00" 1689 | } 1690 | ], 1691 | "aliases": [], 1692 | "minimum-stability": "stable", 1693 | "stability-flags": [], 1694 | "prefer-stable": false, 1695 | "prefer-lowest": false, 1696 | "platform": [], 1697 | "platform-dev": [] 1698 | } 1699 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./tests/Unit 16 | 17 | 18 | 19 | ./tests/Feature 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/Area.php: -------------------------------------------------------------------------------- 1 | xTopLeft = $xTopLeft; 17 | $this->yTopLeft = $yTopLeft; 18 | $this->xBottomRight = $xBottomRight; 19 | $this->yBottomRight = $yBottomRight; 20 | } 21 | 22 | public function xTopLeft() 23 | { 24 | return $this->xTopLeft; 25 | } 26 | 27 | public function yTopLeft() 28 | { 29 | return $this->yTopLeft; 30 | } 31 | 32 | public function xBottomRight() 33 | { 34 | return $this->xBottomRight; 35 | } 36 | 37 | public function yBottomRight() 38 | { 39 | return $this->yBottomRight; 40 | } 41 | 42 | public function coords() 43 | { 44 | return implode(',', [ 45 | $this->xTopLeft, 46 | $this->yTopLeft, 47 | $this->xBottomRight, 48 | $this->yBottomRight, 49 | ]); 50 | } 51 | } -------------------------------------------------------------------------------- /src/Areas.php: -------------------------------------------------------------------------------- 1 | push(new Area($xTopLeft, $yTopLeft, $xBottomRight, $yBottomRight)); 18 | 19 | return $areas; 20 | } 21 | 22 | public function add($xTopLeft, $yTopLeft, $xBottomRight, $yBottomRight) 23 | { 24 | $this->areas[] = new Area($xTopLeft, $yTopLeft, $xBottomRight, $yBottomRight); 25 | 26 | return $this; 27 | } 28 | 29 | public function push(Area $area) 30 | { 31 | $this->areas[] = $area; 32 | 33 | return $this; 34 | } 35 | 36 | public function toDelimitedString($join) 37 | { 38 | $coords = array_map(function(Area $area) { 39 | return $area->coords(); 40 | }, $this->areas); 41 | 42 | return $join . implode($join, $coords); 43 | } 44 | } -------------------------------------------------------------------------------- /src/Camelot.php: -------------------------------------------------------------------------------- 1 | path = $path; 115 | $this->mode = $mode ?? static::MODE_LATTICE; 116 | } 117 | 118 | public static function lattice($path) 119 | { 120 | return new self($path); 121 | } 122 | 123 | public static function stream($path) 124 | { 125 | return new self($path, static::MODE_STREAM); 126 | } 127 | 128 | public function pages(string $pages) 129 | { 130 | $this->pages = $pages; 131 | 132 | return $this; 133 | } 134 | 135 | public function csv() 136 | { 137 | $this->format = 'csv'; 138 | } 139 | 140 | public function save($path) 141 | { 142 | // run the process 143 | $this->runCommand($path); 144 | return $this->getFilesContents($path); 145 | } 146 | 147 | public function extract() 148 | { 149 | $dir = (new TemporaryDirectory())->create(); 150 | $path = $dir->path('extract.txt'); 151 | 152 | $this->runCommand($path); 153 | 154 | $output = $this->getFilesContents($path); 155 | 156 | $dir->delete(); 157 | 158 | return $output; 159 | } 160 | 161 | protected function getFilesContents($filePath) 162 | { 163 | $pathInfo = pathinfo($filePath); 164 | $filename = $pathInfo['filename']; 165 | $directory = $pathInfo['dirname']; 166 | 167 | $files = scandir($directory); 168 | $files = array_values(array_filter($files, function ($file) use ($filename) { 169 | return preg_match("/{$filename}-.*-table-.*\..*/", $file); 170 | })); 171 | 172 | $output = []; 173 | 174 | foreach ($files as $file) { 175 | $output[] = $content = file_get_contents($directory . DIRECTORY_SEPARATOR . $file); 176 | } 177 | 178 | return $output; 179 | } 180 | 181 | protected function runCommand($outputPath = null) 182 | { 183 | $output = $outputPath ? " --output $outputPath" : ""; 184 | $mode = " {$this->mode}"; 185 | $pages = $this->pages ? " --pages {$this->pages}" : ""; 186 | $password = $this->password ? " --password {$this->password}" : ""; 187 | 188 | // Advanced options 189 | $background = $this->processBackgroundLines ? " --process_background " : ""; 190 | $plot = $this->plot ? " -plot {$this->plot}" : ""; 191 | $split = ($this->splitAlongSeparators && $this->columnSeparators) ? " -split" : ""; 192 | $flagSize = $this->flagSize ? " -flag" : ""; 193 | $columnSeparators = $this->columnSeparators ? " -C " . implode(",",$this->columnSeparators) : ""; 194 | $strip = $this->unwantedCharacters ? " -strip '{$this->unwantedCharacters}'" : ""; 195 | $edgeTolerance = $this->edgeTolerance ? " -e {$this->edgeTolerance}" : ""; 196 | $rowTolerance = $this->rowTolerance ? " -r {$this->rowTolerance}" : ""; 197 | $lineScale = $this->lineScale ? " -scale {$this->lineScale}" : ""; 198 | $textShift = $this->textShift ? " -shift " . implode(" -shift ", $this->textShift) : ""; 199 | $copyText = $this->copyTextDirections ? " -copy " . implode(" -copy ", $this->copyTextDirections) : ""; 200 | 201 | // Table areas/regions 202 | $areas = $this->areas ? $this->areas->toDelimitedString(" -T ") : ""; 203 | $regions = $this->regions ? $this->regions->toDelimitedString(" -R ") : ""; 204 | 205 | $cmd = "camelot --format csv {$output}{$pages}{$password}{$flagSize}{$split}{$strip}{$mode}{$textShift}{$copyText}{$lineScale}{$edgeTolerance}{$rowTolerance}{$background}{$plot}{$areas}{$regions}{$columnSeparators} " . $this->path; 206 | 207 | $process = Process::fromShellCommandline($cmd); 208 | $process->run(); 209 | 210 | if (!$process->isSuccessful()) { 211 | $this->throwError($this->path, $process->getErrorOutput(), $cmd); 212 | } 213 | } 214 | 215 | public function json() 216 | { 217 | $this->format = 'json'; 218 | 219 | return $this; 220 | } 221 | 222 | public function html() 223 | { 224 | $this->format = 'html'; 225 | 226 | return $this; 227 | } 228 | 229 | public function sqlite() 230 | { 231 | $this->format = 'sqlite'; 232 | 233 | return $this; 234 | } 235 | 236 | public function excel() 237 | { 238 | $this->format = 'excel'; 239 | 240 | return $this; 241 | } 242 | 243 | public function getFormat() 244 | { 245 | return $this->format; 246 | } 247 | 248 | public function getMode() 249 | { 250 | return $this->mode; 251 | } 252 | 253 | /** 254 | * @return string 255 | */ 256 | public function getPages() 257 | { 258 | return $this->pages; 259 | } 260 | 261 | public function password($password) 262 | { 263 | $this->password = $password; 264 | 265 | return $this; 266 | } 267 | 268 | protected function throwError($path, string $getErrorOutput, string $cmd) 269 | { 270 | if (strpos($getErrorOutput, 'file has not been decrypted') > -1) { 271 | throw new PdfEncryptedException($path); 272 | } 273 | 274 | throw new \Exception("Unexpected Camelot error.\r\nCommand: $cmd\r\nOutput:\r\n-----------\r\n$getErrorOutput"); 275 | } 276 | 277 | public function processBackgroundLines() 278 | { 279 | if ($this->mode !== static::MODE_LATTICE) { 280 | throw new BackgroundLinesNotSupportedException($this->mode); 281 | } 282 | 283 | $this->processBackgroundLines = true; 284 | 285 | return $this; 286 | } 287 | 288 | public function plot($kind = 'text') 289 | { 290 | $this->plot = $kind; 291 | 292 | $this->runCommand(); 293 | 294 | return $this; 295 | } 296 | 297 | public function inAreas(Areas $areas) 298 | { 299 | $this->areas = $areas; 300 | 301 | return $this; 302 | } 303 | 304 | public function inRegions(Areas $regions) 305 | { 306 | $this->regions = $regions; 307 | 308 | return $this; 309 | } 310 | 311 | public function setColumnSeparators(array $xCoords, $split = false) 312 | { 313 | if ($this->mode !== static::MODE_STREAM) { 314 | throw new ColumnSeparatorsNotSupportedException($this->mode); 315 | } 316 | 317 | $this->columnSeparators = $xCoords; 318 | $this->splitAlongSeparators = $split; 319 | 320 | return $this; 321 | } 322 | 323 | public function flagSize($flag = true) 324 | { 325 | $this->flagSize = $flag; 326 | 327 | return $this; 328 | } 329 | 330 | public function strip(string $unwantedCharacters) 331 | { 332 | $this->unwantedCharacters = $unwantedCharacters; 333 | 334 | return $this; 335 | } 336 | 337 | public function setEdgeTolerance(int $edgeTolerance) 338 | { 339 | $this->edgeTolerance = $edgeTolerance; 340 | 341 | return $this; 342 | } 343 | 344 | public function setRowTolerance(int $rowTolerance) 345 | { 346 | $this->rowTolerance = $rowTolerance; 347 | 348 | return $this; 349 | } 350 | 351 | public function setLineScale(int $lineScale) 352 | { 353 | $this->lineScale = $lineScale; 354 | 355 | return $this; 356 | } 357 | 358 | public function shiftText(...$directions) 359 | { 360 | $this->textShift = $directions; 361 | 362 | return $this; 363 | } 364 | 365 | public function copyTextSpanningCells(...$directions) 366 | { 367 | $this->copyTextDirections = $directions; 368 | 369 | return $this; 370 | } 371 | 372 | } -------------------------------------------------------------------------------- /src/Exceptions/BackgroundLinesNotSupportedException.php: -------------------------------------------------------------------------------- 1 | featureName() . ". It can be used with ". $this->validModesString()."."); 12 | } 13 | 14 | private function validModesString() 15 | { 16 | $validModes = $this->validModes(); 17 | $modes = implode(' | ', $validModes); 18 | 19 | return count($validModes) > 1 ? $modes . ' modes' : $modes . ' mode'; 20 | } 21 | abstract protected function validModes(): array; 22 | abstract protected function featureName(): string; 23 | } -------------------------------------------------------------------------------- /src/Exceptions/PdfEncryptedException.php: -------------------------------------------------------------------------------- 1 | password('my_password')."); 14 | } 15 | } -------------------------------------------------------------------------------- /tests/Feature/AdvancedProcessingTest.php: -------------------------------------------------------------------------------- 1 | file('background_lines_1.pdf')) 20 | ->processBackgroundLines() 21 | ->extract(); 22 | 23 | $csv = $this->csvFromString($tables[1]); 24 | $csv->setHeaderOffset(0); 25 | 26 | $this->assertCount(8, $header = $csv->getHeader()); 27 | $this->assertEquals('State', $header[0]); 28 | } 29 | 30 | /** 31 | * @test 32 | */ 33 | public function cannot_use_background_line_processing_with_stream() 34 | { 35 | $this->expectException(BackgroundLinesNotSupportedException::class); 36 | 37 | Camelot::stream($this->file('background_lines_1.pdf')) 38 | ->processBackgroundLines() 39 | ->extract(); 40 | } 41 | 42 | /** 43 | * @test 44 | */ 45 | public function set_column_separators() 46 | { 47 | $tables = Camelot::stream($this->file('column_separators.pdf')) 48 | ->setColumnSeparators([72,95,209,327,442,529,566,606,683]) 49 | ->extract(); 50 | 51 | $this->assertCount(1, $tables); 52 | $csv = $this->csvFromString($tables[0]); 53 | $csv->setHeaderOffset(3); 54 | $this->assertEquals('LICENSE', $csv->getHeader()[0]); 55 | $this->assertEquals('PREMISE', $csv->getHeader()[4]); 56 | } 57 | 58 | /** 59 | * @test 60 | */ 61 | public function cannot_set_column_separators_for_lattice_mode() 62 | { 63 | $this->expectException(ColumnSeparatorsNotSupportedException::class); 64 | 65 | Camelot::lattice($this->file('column_separators.pdf')) 66 | ->setColumnSeparators([72,95,209,327,442,529,566,606,683]) 67 | ->extract(); 68 | } 69 | 70 | /** 71 | * @test 72 | */ 73 | public function enable_split_text_along_separators() 74 | { 75 | $tables = Camelot::stream($this->file('column_separators.pdf')) 76 | ->setColumnSeparators([72,95,209,327,442,529,566,606,683], true) 77 | ->extract(); 78 | 79 | $this->assertCount(1, $tables); 80 | $csv = $this->csvFromString($tables[0]); 81 | $csv->setHeaderOffset(4); 82 | $this->assertEquals('NUMBER', $csv->getHeader()[0]); 83 | $this->assertEquals('TYPE', $csv->getHeader()[1]); 84 | } 85 | 86 | /** 87 | * @test 88 | */ 89 | public function flag_superscripts_and_subscripts() 90 | { 91 | $tables = Camelot::stream($this->file('superscript.pdf')) 92 | ->flagSize() 93 | ->extract(); 94 | 95 | $this->assertCount(1, $tables); 96 | $csv = $this->csvFromString($tables[0]); 97 | 98 | $this->assertStringContainsString('', $csv->fetchOne(18)[2]); 99 | $this->assertStringContainsString('', $csv->fetchOne(18)[2]); 100 | } 101 | 102 | /** 103 | * @test 104 | */ 105 | public function strip_characters_from_text() 106 | { 107 | $tables = Camelot::stream($this->file('strip.pdf')) 108 | ->strip(" .\n") // space, period or new lines 109 | ->extract(); 110 | 111 | $csv = $this->csvFromString($tables[0]); 112 | $this->assertEquals('Robbery', $csv->fetchOne(12)[0]); 113 | } 114 | 115 | /** 116 | * @test 117 | */ 118 | public function can_adjust_edge_tolerance() 119 | { 120 | $tables = Camelot::stream($this->file('edge_tol.pdf')) 121 | ->strip("\n") 122 | ->setEdgeTolerance(500) 123 | ->extract(); 124 | 125 | $this->assertStringContainsString('Total investment result per unit', $tables[0]); 126 | } 127 | 128 | /** 129 | * @test 130 | */ 131 | public function can_adjust_row_tolerance() 132 | { 133 | $tables = Camelot::stream($this->file('row_tol.pdf')) 134 | ->strip("\n") 135 | ->setRowTolerance(10) 136 | ->extract(); 137 | 138 | $this->assertCount(1, $tables); 139 | $csv = $this->csvFromString($tables[0]); 140 | $csv->setHeaderOffset(0); 141 | 142 | $this->assertEquals('Nombre Entidad', $csv->getHeader()[1]); 143 | } 144 | 145 | /** 146 | * @test 147 | */ 148 | public function can_set_line_scale() 149 | { 150 | $tables = Camelot::lattice($this->file('short_lines.pdf')) 151 | ->strip("-\n") 152 | ->setLineScale(40) 153 | ->extract(); 154 | 155 | $this->assertCount(1, $tables); 156 | $csv = $this->csvFromString($tables[0]); 157 | $csv->setHeaderOffset(0); 158 | 159 | $this->assertEquals('Prevalence', $csv->getHeader()[3]); 160 | $this->assertEquals('C.I*', $csv->getHeader()[4]); 161 | $this->assertEquals('RelativePrecision', $csv->getHeader()[5]); 162 | $this->assertEquals('Sample sizeper State', $csv->getHeader()[6]); 163 | } 164 | 165 | /** 166 | * @test 167 | */ 168 | public function can_set_text_shift_in_spanning_cells() 169 | { 170 | $tables = Camelot::lattice($this->file('short_lines.pdf')) 171 | ->strip("-\n") 172 | ->setLineScale(40) 173 | ->shiftText('r', 'b') 174 | ->extract(); 175 | 176 | $this->assertCount(1, $tables); 177 | $csv = $this->csvFromString($tables[0]); 178 | 179 | $row = $csv->fetchOne(3); 180 | $this->assertEquals(2400, $row[1]); 181 | } 182 | 183 | /** 184 | * @test 185 | */ 186 | public function can_copy_text_in_spanning_cells() 187 | { 188 | $tables = Camelot::lattice($this->file('copy_text.pdf')) 189 | ->copyTextSpanningCells('v') 190 | ->extract(); 191 | 192 | $this->assertCount(1, $tables); 193 | $csv = $this->csvFromString($tables[0]); 194 | 195 | $this->assertEquals(4, $csv->fetchOne(4)[0]); 196 | $this->assertEquals('West Bengal', $csv->fetchOne(4)[1]); 197 | 198 | $this->assertEquals(4, $csv->fetchOne(5)[0]); 199 | $this->assertEquals('West Bengal', $csv->fetchOne(5)[1]); 200 | } 201 | } -------------------------------------------------------------------------------- /tests/Feature/EncryptedPdfTest.php: -------------------------------------------------------------------------------- 1 | expectException(PdfEncryptedException::class); 19 | Camelot::lattice($this->file('health_protected.pdf'))->extract(); 20 | } 21 | 22 | /** 23 | * @test 24 | */ 25 | public function can_set_password_to_read_encrypted_pdf() 26 | { 27 | $tables = Camelot::lattice($this->file('health_protected.pdf')) 28 | ->password('ownerpass') 29 | ->extract(); 30 | 31 | $this->assertCount(1, $tables); 32 | } 33 | } -------------------------------------------------------------------------------- /tests/Feature/PagesTest.php: -------------------------------------------------------------------------------- 1 | pages($pages = '1-2,4,5-end'); 18 | 19 | $this->assertEquals($pages,$camelot->getPages()); 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /tests/Feature/ParseExportTest.php: -------------------------------------------------------------------------------- 1 | file('foo_lattice.pdf')) 15 | ->extract()[0]; 16 | 17 | $csv = $this->csvFromString($tables); 18 | $csv->setHeaderOffset(0); 19 | 20 | $this->assertCount(7, $csv->getHeader()); 21 | } 22 | 23 | /** 24 | * @test 25 | */ 26 | public function can_set_stream_mode() 27 | { 28 | $tables = Camelot::stream($this->file('health_stream.pdf')) 29 | ->extract()[0]; 30 | 31 | $csv = $this->csvFromString($tables); 32 | $csv->setHeaderOffset(1); 33 | 34 | $this->assertCount(8, $header = $csv->getHeader()); 35 | $this->assertEquals('States-A', $header[0]); 36 | } 37 | 38 | /** 39 | * @test 40 | */ 41 | public function can_set_output_filename_base() 42 | { 43 | Camelot::stream($this->file('health_stream.pdf')) 44 | ->save(__DIR__ . '/output-custom.txt'); 45 | 46 | $this->assertFileExists(__DIR__ . '/output-custom-page-1-table-1.txt'); 47 | unlink(__DIR__ . '/output-custom-page-1-table-1.txt'); 48 | } 49 | 50 | /** 51 | * @test 52 | */ 53 | public function can_set_output_format() 54 | { 55 | $camelot = Camelot::lattice($this->file('foo_lattice.pdf')); 56 | $this->assertEquals('csv', $camelot->getFormat()); 57 | 58 | $camelot->json(); 59 | $this->assertEquals('json', $camelot->getFormat()); 60 | 61 | $camelot->csv(); 62 | $this->assertEquals('csv', $camelot->getFormat()); 63 | 64 | $camelot->html(); 65 | $this->assertEquals('html', $camelot->getFormat()); 66 | 67 | $camelot->sqlite(); 68 | $this->assertEquals('sqlite', $camelot->getFormat()); 69 | 70 | $camelot->excel(); 71 | $this->assertEquals('excel', $camelot->getFormat()); 72 | } 73 | 74 | /** 75 | * @test 76 | */ 77 | public function defaults_to_lattice_algorithm() 78 | { 79 | $camelot = new Camelot('foo.pdf'); 80 | $this->assertEquals('lattice', $camelot->getMode()); 81 | } 82 | } -------------------------------------------------------------------------------- /tests/Feature/TableHintsTest.php: -------------------------------------------------------------------------------- 1 | file('table_areas.pdf')) 21 | ->inAreas($area) 22 | ->extract(); 23 | 24 | $this->assertCount(1, $tables); 25 | $csv = $this->csvFromString($tables[0]); 26 | $csv->setHeaderOffset(1); 27 | $this->assertEquals('Payroll Period', $csv->getHeader()[0]); 28 | } 29 | 30 | /** 31 | * @test 32 | */ 33 | public function can_set_more_than_one_table_area() 34 | { 35 | $area = Areas::from(89, 694,529,446) 36 | ->add(89, 383, 529, 143) 37 | ; 38 | 39 | $tables = Camelot::stream($this->file('twotables_2.pdf')) 40 | ->inAreas($area) 41 | ->extract(); 42 | 43 | $this->assertCount(2, $tables); 44 | $csv = $this->csvFromString($tables[0]); 45 | $csv->setHeaderOffset(1); 46 | 47 | $this->assertEquals('State', $csv->getHeader()[0]); 48 | $this->assertEquals('n', $csv->getHeader()[1]); 49 | 50 | $csv = $this->csvFromString($tables[1]); 51 | $csv->setHeaderOffset(1); 52 | $this->assertEquals('State', $csv->getHeader()[0]); 53 | $this->assertEquals('n', $csv->getHeader()[1]); 54 | } 55 | 56 | /** 57 | * @test 58 | */ 59 | public function can_suggest_table_region() 60 | { 61 | $area = Areas::from(170,370,560,270); 62 | 63 | $tables = Camelot::lattice($this->file('table_region.pdf')) 64 | ->inRegions($area) 65 | ->extract(); 66 | 67 | $this->assertCount(1, $tables); 68 | $csv = $this->csvFromString($tables[0]); 69 | $csv->setHeaderOffset(0); 70 | 71 | $this->assertEquals('Età dell’Assicurato 72 | all’epoca del decesso', $csv->getHeader()[0]); 73 | } 74 | } -------------------------------------------------------------------------------- /tests/Helpers/CsvAssertions.php: -------------------------------------------------------------------------------- 1 |