diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b65eed2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/coverage/* +/vendor/* +/php-epub-meta.sublime-* +/clover.xml +/composer.phar + diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..4e33e71 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,18 @@ +language: php +php: + - 7.0 + - 5.6 + - 5.5 + - hhvm +before_script: + - composer selfupdate + - composer install +script: + - phpunit +after_success: + - chmod +x test/publishCoverage.sh + - test/publishCoverage.sh +matrix: + allow_failures: + - php: hhvm + - php: 5.5 diff --git a/LICENSE b/LICENSE index 128bf1f..1b10fcb 100644 --- a/LICENSE +++ b/LICENSE @@ -1,3 +1,5 @@ +MIT License + Copyright (c) 2012 Andreas Gohr Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/README b/README.md similarity index 60% rename from README rename to README.md index 21927da..1956ee3 100644 --- a/README +++ b/README.md @@ -1,4 +1,5 @@ -====== PHP EPub Meta ====== +PHP EPub Meta +============= This project aims to create a PHP class for reading and writing metadata included in the EPub ebook format. @@ -9,7 +10,9 @@ Please see the issue tracker for what's missing. Forks and pull requests welcome. -===== About the EPub Manager Web Interface ===== + +About the EPub Manager Web Interface +------------------------------------ The manager expects your ebooks in a single flat directory (no subfolders). The location of that directory has to be configured at the top of the index.php file. @@ -17,12 +20,25 @@ location of that directory has to be configured at the top of the index.php file All the epubs need to be read- and writable by the webserver. The manager also makes some assumption on how the files should be named. The -format is: "-.epub". Commas will be replaced by __ and -spaces are replaced by _. +format is: `<Author file-as>-<Title>.epub`. Commas will be replaced by `__` and +spaces are replaced by `_`. -Note that the manager will RENAME your files to that form when saving. +Note that the manager will **RENAME** your files to that form when saving. Using the "Lookup Book Data" link will open a dialog that searches the book at Google Books you can use the found data using the "fill in" and "replace" buttons. The former will only fill empty fields, while the latter will replace all data. Author filling is missing currently. + + +Installing via Composer +======================= + +You can use this package in your projects with [Composer](https://getcomposer.org/). Just +add these lines to your project's `composer.json`: + +``` + "require": { + "seblucas/php-epub-meta": "dev-master", + } +``` diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..b1551bb --- /dev/null +++ b/composer.json @@ -0,0 +1,41 @@ +{ + "name": "splitbrain/php-epub-meta", + "type": "library", + "description": "Reading and writing metadata included in the EPub ebook format", + "keywords": [ + "epub", + "metadata", + "ebook" + ], + "homepage": "https://github.com/splitbrain/php-epub-meta", + "license": "MIT", + "authors": [ + { + "name": "Andreas Gohr", + "email": "andi@splitbrain.org", + "homepage": "https://www.splitbrain.org/", + "role": "Developer" + }, + { + "name": "Sébastien Lucas", + "email": "sebastien@slucas.fr", + "homepage": "http://www.slucas.fr/", + "role": "Developer" + } + ], + "require": { + "php": ">=5.3.0", + "ext-xml": "*", + "ext-zip": "*", + "seblucas/tbszip": "~2.16.0" + }, + "require-dev": { + "phpunit/phpunit": "4.*" + }, + "autoload": { + "psr-4": { + "splitbrain\\epubmeta\\test\\": "test", + "splitbrain\\epubmeta\\": "lib" + } + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..9bb8ca6 --- /dev/null +++ b/composer.lock @@ -0,0 +1,1180 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "hash": "bf907b849328209271870729fe288382", + "content-hash": "d0841a1d14a8e6ea6305431ea76f1267", + "packages": [ + { + "name": "seblucas/tbszip", + "version": "2.16.1", + "source": { + "type": "git", + "url": "https://github.com/seblucas/tbszip.git", + "reference": "2c50bf309bb4431a24e206f164fdb4a2e6b10b7f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/seblucas/tbszip/zipball/2c50bf309bb4431a24e206f164fdb4a2e6b10b7f", + "reference": "2c50bf309bb4431a24e206f164fdb4a2e6b10b7f", + "shasum": "" + }, + "require": { + "ext-zlib": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "5.4.*" + }, + "type": "library", + "autoload": { + "classmap": [ + "tbszip.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1+" + ], + "authors": [ + { + "name": "Skrol29", + "homepage": "http://www.tinybutstrong.com/", + "role": "Developer" + }, + { + "name": "Sébastien Lucas", + "email": "sebastien@slucas.fr", + "homepage": "http://www.slucas.fr/", + "role": "Developer" + } + ], + "description": "Work with zip archives without making temporary files or needing binaries", + "homepage": "http://www.tinybutstrong.com/tools.php", + "keywords": [ + "archive", + "compression", + "zip" + ], + "time": "2016-07-03 12:26:16" + } + ], + "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "shasum": "" + }, + "require": { + "php": ">=5.3,<8.0-DEV" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2015-06-14 21:17:01" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "1.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/144c307535e82c8fdcaacbcfc1d6d8eeb896687c", + "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2015-12-27 11:43:31" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "9270140b940ff02e58ec577c237274e92cd40cdd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9270140b940ff02e58ec577c237274e92cd40cdd", + "reference": "9270140b940ff02e58ec577c237274e92cd40cdd", + "shasum": "" + }, + "require": { + "php": ">=5.5", + "phpdocumentor/reflection-common": "^1.0@dev", + "phpdocumentor/type-resolver": "^0.2.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2016-06-10 09:48:41" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "0.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "b39c7a5b194f9ed7bd0dd345c751007a41862443" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/b39c7a5b194f9ed7bd0dd345c751007a41862443", + "reference": "b39c7a5b194f9ed7bd0dd345c751007a41862443", + "shasum": "" + }, + "require": { + "php": ">=5.5", + "phpdocumentor/reflection-common": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^5.2||^4.8.24" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "time": "2016-06-10 07:14:17" + }, + { + "name": "phpspec/prophecy", + "version": "v1.6.1", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "58a8137754bc24b25740d4281399a4a3596058e0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/58a8137754bc24b25740d4281399a4a3596058e0", + "reference": "58a8137754bc24b25740d4281399a4a3596058e0", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", + "sebastian/comparator": "^1.1", + "sebastian/recursion-context": "^1.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, + "autoload": { + "psr-0": { + "Prophecy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2016-06-07 08:13:47" + }, + { + "name": "phpunit/php-code-coverage", + "version": "2.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979", + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "phpunit/php-file-iterator": "~1.3", + "phpunit/php-text-template": "~1.2", + "phpunit/php-token-stream": "~1.3", + "sebastian/environment": "^1.3.2", + "sebastian/version": "~1.0" + }, + "require-dev": { + "ext-xdebug": ">=2.1.4", + "phpunit/phpunit": "~4" + }, + "suggest": { + "ext-dom": "*", + "ext-xdebug": ">=2.2.1", + "ext-xmlwriter": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2015-10-06 15:47:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0", + "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2015-06-21 13:08:43" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21 13:50:34" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/38e9124049cf1a164f1e4537caf19c99bf1eb260", + "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4|~5" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2016-05-12 18:03:57" + }, + { + "name": "phpunit/php-token-stream", + "version": "1.4.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", + "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2015-09-15 10:49:45" + }, + { + "name": "phpunit/phpunit", + "version": "4.8.26", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "fc1d8cd5b5de11625979125c5639347896ac2c74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fc1d8cd5b5de11625979125c5639347896ac2c74", + "reference": "fc1d8cd5b5de11625979125c5639347896ac2c74", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=5.3.3", + "phpspec/prophecy": "^1.3.1", + "phpunit/php-code-coverage": "~2.1", + "phpunit/php-file-iterator": "~1.4", + "phpunit/php-text-template": "~1.2", + "phpunit/php-timer": "^1.0.6", + "phpunit/phpunit-mock-objects": "~2.3", + "sebastian/comparator": "~1.1", + "sebastian/diff": "~1.2", + "sebastian/environment": "~1.3", + "sebastian/exporter": "~1.2", + "sebastian/global-state": "~1.0", + "sebastian/version": "~1.0", + "symfony/yaml": "~2.1|~3.0" + }, + "suggest": { + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.8.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2016-05-17 03:09:28" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "2.3.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983", + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": ">=5.3.3", + "phpunit/php-text-template": "~1.2", + "sebastian/exporter": "~1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "time": "2015-10-02 06:51:40" + }, + { + "name": "sebastian/comparator", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "937efb279bd37a375bcadf584dec0726f84dbf22" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22", + "reference": "937efb279bd37a375bcadf584dec0726f84dbf22", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "http://www.github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2015-07-26 15:48:44" + }, + { + "name": "sebastian/diff", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e", + "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2015-12-08 07:14:41" + }, + { + "name": "sebastian/environment", + "version": "1.3.7", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "4e8f0da10ac5802913afc151413bc8c53b6c2716" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/4e8f0da10ac5802913afc151413bc8c53b6c2716", + "reference": "4e8f0da10ac5802913afc151413bc8c53b6c2716", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2016-05-17 03:18:57" + }, + { + "name": "sebastian/exporter", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4", + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/recursion-context": "~1.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2016-06-17 09:04:28" + }, + { + "name": "sebastian/global-state", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2015-10-12 03:26:01" + }, + { + "name": "sebastian/recursion-context", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "913401df809e99e4f47b27cdd781f4a258d58791" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/913401df809e99e4f47b27cdd781f4a258d58791", + "reference": "913401df809e99e4f47b27cdd781f4a258d58791", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2015-11-11 19:50:13" + }, + { + "name": "sebastian/version", + "version": "1.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "shasum": "" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2015-06-21 13:59:46" + }, + { + "name": "symfony/yaml", + "version": "v3.1.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "2884c26ce4c1d61aebf423a8b912950fe7c764de" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/2884c26ce4c1d61aebf423a8b912950fe7c764de", + "reference": "2884c26ce4c1d61aebf423a8b912950fe7c764de", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2016-06-29 05:41:56" + }, + { + "name": "webmozart/assert", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "30eed06dd6bc88410a4ff7f77b6d22f3ce13dbde" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/30eed06dd6bc88410a4ff7f77b6d22f3ce13dbde", + "reference": "30eed06dd6bc88410a4ff7f77b6d22f3ce13dbde", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2015-08-24 13:29:44" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.3.0", + "ext-xml": "*", + "ext-zip": "*" + }, + "platform-dev": [] +} diff --git a/epub.php b/epub.php deleted file mode 100644 index 6895a67..0000000 --- a/epub.php +++ /dev/null @@ -1,536 +0,0 @@ -<?php -/** - * PHP EPub Meta library - * - * @author Andreas Gohr <andi@splitbrain.org> - */ -class EPub { - public $xml; //FIXME change to protected, later - protected $xpath; - protected $file; - protected $meta; - protected $namespaces; - protected $imagetoadd=''; - - /** - * Constructor - * - * @param string $file path to epub file to work on - * @throws Exception if metadata could not be loaded - */ - public function __construct($file){ - // open file - $this->file = $file; - $zip = new ZipArchive(); - if(!@$zip->open($this->file)){ - throw new Exception('Failed to read epub file'); - } - - // read container data - $data = $zip->getFromName('META-INF/container.xml'); - if($data == false){ - throw new Exception('Failed to access epub container data'); - } - $xml = new DOMDocument(); - $xml->registerNodeClass('DOMElement','EPubDOMElement'); - $xml->loadXML($data); - $xpath = new EPubDOMXPath($xml); - $nodes = $xpath->query('//n:rootfiles/n:rootfile[@media-type="application/oebps-package+xml"]'); - $this->meta = $nodes->item(0)->attr('full-path'); - - // load metadata - $data = $zip->getFromName($this->meta); - if(!$data){ - throw new Exception('Failed to access epub metadata'); - } - $this->xml = new DOMDocument(); - $this->xml->registerNodeClass('DOMElement','EPubDOMElement'); - $this->xml->loadXML($data); - $this->xml->formatOutput = true; - $this->xpath = new EPubDOMXPath($this->xml); - - $zip->close(); - } - - /** - * file name getter - */ - public function file(){ - return $this->file; - } - - /** - * Writes back all meta data changes - */ - public function save(){ - $zip = new ZipArchive(); - $res = @$zip->open($this->file, ZipArchive::CREATE); - if($res === false){ - throw new Exception('Failed to write back metadata'); - } - $zip->addFromString($this->meta,$this->xml->saveXML()); - // add the cover image - if($this->imagetoadd){ - $path = dirname('/'.$this->meta).'/php-epub-meta-cover.img'; // image path is relative to meta file - $path = ltrim($path,'/'); - - $zip->addFromString($path,file_get_contents($this->imagetoadd)); - $this->imagetoadd=''; - } - $zip->close(); - } - - /** - * Get or set the book author(s) - * - * Authors should be given with a "file-as" and a real name. The file as - * is used for sorting in e-readers. - * - * Example: - * - * array( - * 'Pratchett, Terry' => 'Terry Pratchett', - * 'Simpson, Jacqeline' => 'Jacqueline Simpson', - * ) - * - * @params array $authors - */ - public function Authors($authors=false){ - // set new data - if($authors !== false){ - // Author where given as a comma separated list - if(is_string($authors)){ - if($authors == ''){ - $authors = array(); - }else{ - $authors = explode(',',$authors); - $authors = array_map('trim',$authors); - } - } - - // delete existing nodes - $nodes = $this->xpath->query('//opf:metadata/dc:creator[@opf:role="aut"]'); - foreach($nodes as $node) $node->delete(); - - // add new nodes - $parent = $this->xpath->query('//opf:metadata')->item(0); - foreach($authors as $as => $name){ - if(is_int($as)) $as = $name; //numeric array given - $node = $parent->newChild('dc:creator',$name); - $node->attr('opf:role', 'aut'); - $node->attr('opf:file-as', $as); - } - - $this->reparse(); - } - - // read current data - $rolefix = false; - $authors = array(); - $nodes = $this->xpath->query('//opf:metadata/dc:creator[@opf:role="aut"]'); - if($nodes->length == 0){ - // no nodes where found, let's try again without role - $nodes = $this->xpath->query('//opf:metadata/dc:creator'); - $rolefix = true; - } - foreach($nodes as $node){ - $name = $node->nodeValue; - $as = $node->attr('opf:file-as'); - if(!$as){ - $as = $name; - $node->attr('opf:file-as',$as); - } - if($rolefix){ - $node->attr('opf:role','aut'); - } - $authors[$as] = $name; - } - return $authors; - } - - /** - * Set or get the book title - * - * @param string $title - */ - public function Title($title=false){ - return $this->getset('dc:title',$title); - } - - /** - * Set or get the book's language - * - * @param string $lang - */ - public function Language($lang=false){ - return $this->getset('dc:language',$lang); - } - - /** - * Set or get the book' publisher info - * - * @param string $publisher - */ - public function Publisher($publisher=false){ - return $this->getset('dc:publisher',$publisher); - } - - /** - * Set or get the book's copyright info - * - * @param string $rights - */ - public function Copyright($rights=false){ - return $this->getset('dc:rights',$rights); - } - - /** - * Set or get the book's description - * - * @param string $description - */ - public function Description($description=false){ - return $this->getset('dc:description',$description); - } - - /** - * Set or get the book's ISBN number - * - * @param string $isbn - */ - public function ISBN($isbn=false){ - return $this->getset('dc:identifier',$isbn,'opf:scheme','ISBN'); - } - - /** - * Set or get the Google Books ID - * - * @param string $google - */ - public function Google($google=false){ - return $this->getset('dc:identifier',$google,'opf:scheme','GOOGLE'); - } - - /** - * Set or get the Amazon ID of the book - * - * @param string $amazon - */ - public function Amazon($amazon=false){ - return $this->getset('dc:identifier',$amazon,'opf:scheme','AMAZON'); - } - - /** - * Set or get the book's subjects (aka. tags) - * - * Subject should be given as array, but a comma separated string will also - * be accepted. - * - * @param array $subjects - */ - public function Subjects($subjects=false){ - // setter - if($subjects !== false){ - if(is_string($subjects)){ - if($subjects === ''){ - $subjects = array(); - }else{ - $subjects = explode(',',$subjects); - $subjects = array_map('trim',$subjects); - } - } - - // delete previous - $nodes = $this->xpath->query('//opf:metadata/dc:subject'); - foreach($nodes as $node){ - $node->delete(); - } - // add new ones - $parent = $this->xpath->query('//opf:metadata')->item(0); - foreach($subjects as $subj){ - $node = $this->xml->createElement('dc:subject',htmlspecialchars($subj)); - $node = $parent->appendChild($node); - } - - $this->reparse(); - } - - //getter - $subjects = array(); - $nodes = $this->xpath->query('//opf:metadata/dc:subject'); - foreach($nodes as $node){ - $subjects[] = $node->nodeValue; - } - return $subjects; - } - - /** - * Read the cover data - * - * Returns an associative array with the following keys: - * - * mime - filetype (usually image/jpeg) - * data - the binary image data - * found - the internal path, or false if no image is set in epub - * - * When no image is set in the epub file, the binary data for a transparent - * GIF pixel is returned. - * - * When adding a new image this function return no or old data because the - * image contents are not in the epub file, yet. The image will be added when - * the save() method is called. - * - * @param string $path local filesystem path to a new cover image - * @param string $mime mime type of the given file - * @return array - */ - public function Cover($path=false, $mime=false){ - // set cover - if($path !== false){ - // remove current pointer - $nodes = $this->xpath->query('//opf:metadata/opf:meta[@name="cover"]'); - foreach($nodes as $node) $node->delete(); - // remove previous manifest entries if they where made by us - $nodes = $this->xpath->query('//opf:manifest/opf:item[@id="php-epub-meta-cover"]'); - foreach($nodes as $node) $node->delete(); - - if($path){ - // add pointer - $parent = $this->xpath->query('//opf:metadata')->item(0); - $node = $parent->newChild('opf:meta'); - $node->attr('opf:name','cover'); - $node->attr('opf:content','php-epub-meta-cover'); - - // add manifest - $parent = $this->xpath->query('//opf:manifest')->item(0); - $node = $parent->newChild('opf:item'); - $node->attr('id','php-epub-meta-cover'); - $node->attr('opf:href','php-epub-meta-cover.img'); - $node->attr('opf:media-type',$mime); - - // remember path for save action - $this->imagetoadd = $path; - } - - $this->reparse(); - } - - // load cover - $nodes = $this->xpath->query('//opf:metadata/opf:meta[@name="cover"]'); - if(!$nodes->length) return $this->no_cover(); - $coverid = (String) $nodes->item(0)->attr('opf:content'); - if(!$coverid) return $this->no_cover(); - - $nodes = $this->xpath->query('//opf:manifest/opf:item[@id="'.$coverid.'"]'); - if(!$nodes->length) return $this->no_cover(); - $mime = $nodes->item(0)->attr('opf:media-type'); - $path = $nodes->item(0)->attr('opf:href'); - $path = dirname('/'.$this->meta).'/'.$path; // image path is relative to meta file - $path = ltrim($path,'/'); - - $zip = new ZipArchive(); - if(!@$zip->open($this->file)){ - throw new Exception('Failed to read epub file'); - } - $data = $zip->getFromName($path); - - return array( - 'mime' => $mime, - 'data' => $data, - 'found' => $path - ); - } - - /** - * A simple getter/setter for simple meta attributes - * - * It should only be used for attributes that are expected to be unique - * - * @param string $item XML node to set/get - * @param string $value New node value - * @param string $att Attribute name - * @param string $aval Attribute value - */ - protected function getset($item,$value=false,$att=false,$aval=false){ - // construct xpath - $xpath = '//opf:metadata/'.$item; - if($att){ - $xpath .= "[@$att=\"$aval\"]"; - } - - // set value - if($value !== false){ - $value = htmlspecialchars($value); - $nodes = $this->xpath->query($xpath); - if($nodes->length == 1 ){ - if($value === ''){ - // the user want's to empty this value -> delete the node - $nodes->item(0)->delete(); - }else{ - // replace value - $nodes->item(0)->nodeValue = $value; - } - }else{ - // if there are multiple matching nodes for some reason delete - // them. we'll replace them all with our own single one - foreach($nodes as $n) $n->delete(); - // readd them - if($value){ - $parent = $this->xpath->query('//opf:metadata')->item(0); - $node = $this->xml->createElement($item,$value); - $node = $parent->appendChild($node); - if($att) $node->attr($att,$aval); - } - } - - $this->reparse(); - } - - // get value - $nodes = $this->xpath->query($xpath); - if($nodes->length){ - return $nodes->item(0)->nodeValue; - }else{ - return ''; - } - } - - /** - * Return a not found response for Cover() - */ - protected function no_cover(){ - return array( - 'data' => base64_decode('R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAEALAAAAAABAAEAAAIBTAA7'), - 'mime' => 'image/gif', - 'found' => false - ); - } - - /** - * Reparse the DOM tree - * - * I had to rely on this because otherwise xpath failed to find the newly - * added nodes - */ - protected function reparse() { - $this->xml->loadXML($this->xml->saveXML()); - $this->xpath = new EPubDOMXPath($this->xml); - } -} - -class EPubDOMXPath extends DOMXPath { - public function __construct(DOMDocument $doc){ - parent::__construct($doc); - - if(is_a($doc->documentElement, 'EPubDOMElement')){ - foreach($doc->documentElement->namespaces as $ns => $url){ - $this->registerNamespace($ns,$url); - } - } - } -} - -class EPubDOMElement extends DOMElement { - public $namespaces = array( - 'n' => 'urn:oasis:names:tc:opendocument:xmlns:container', - 'opf' => 'http://www.idpf.org/2007/opf', - 'dc' => 'http://purl.org/dc/elements/1.1/' - ); - - - public function __construct($name, $value='', $namespaceURI=''){ - list($ns,$name) = $this->splitns($name); - $value = htmlspecialchars($value); - if(!$namespaceURI && $ns){ - $namespaceURI = $this->namespaces[$ns]; - } - parent::__construct($name, $value, $namespaceURI); - } - - - /** - * Create and append a new child - * - * Works with our epub namespaces and omits default namespaces - */ - public function newChild($name, $value=''){ - list($ns,$local) = $this->splitns($name); - if($ns){ - $nsuri = $this->namespaces[$ns]; - if($this->isDefaultNamespace($nsuri)){ - $name = $local; - $nsuri = ''; - } - } - - // this doesn't call the construcor: $node = $this->ownerDocument->createElement($name,$value); - $node = new EPubDOMElement($name,$value,$nsuri); - return $this->appendChild($node); - } - - /** - * Split given name in namespace prefix and local part - * - * @param string $name - * @return array (namespace, name) - */ - public function splitns($name){ - $list = explode(':',$name,2); - if(count($list) < 2) array_unshift($list,''); - return $list; - } - - /** - * Simple EPub namespace aware attribute accessor - */ - public function attr($attr,$value=null){ - list($ns,$attr) = $this->splitns($attr); - - $nsuri = ''; - if($ns){ - $nsuri = $this->namespaces[$ns]; - if(!$this->namespaceURI){ - if($this->isDefaultNamespace($nsuri)){ - $nsuri = ''; - } - }elseif($this->namespaceURI == $nsuri){ - $nsuri = ''; - } - } - - if(!is_null($value)){ - if($value === false){ - // delete if false was given - if($nsuri){ - $this->removeAttributeNS($nsuri,$attr); - }else{ - $this->removeAttribute($attr); - } - }else{ - // modify if value was given - if($nsuri){ - $this->setAttributeNS($nsuri,$attr,$value); - }else{ - $this->setAttribute($attr,$value); - } - } - }else{ - // return value if none was given - if($nsuri){ - return $this->getAttributeNS($nsuri,$attr); - }else{ - return $this->getAttribute($attr); - } - } - } - - /** - * Remove this node from the DOM - */ - public function delete(){ - $this->parentNode->removeChild($this); - } - -} - - diff --git a/index.php b/index.php index 57bb31b..3744db9 100644 --- a/index.php +++ b/index.php @@ -1,54 +1,54 @@ <?php // modify this to point to your book directory - $bookdir = '/home/andi/Dropbox/ebooks/'; +use splitbrain\epubmeta\EPub; - - error_reporting(E_ALL ^ E_NOTICE); +$bookdir = '/home/andi/Dropbox/ebooks/'; // proxy google requests - if(isset($_GET['api'])){ + if (isset($_GET['api'])) { header('application/json; charset=UTF-8'); - echo file_get_contents('https://www.googleapis.com/books/v1/volumes?q='.rawurlencode($_GET['api']).'&maxResults=25&printType=books&projection=full'); + echo file_get_contents('https://www.googleapis.com/books/v1/volumes?q=' . rawurlencode($_GET['api']) . '&maxResults=25&printType=books&projection=full'); exit; } - require('util.php'); + require_once dirname(__FILE__) . '/vendor/autoload.php'; + require_once dirname(__FILE__) . '/util.php'; - // load epub data - require('epub.php'); - if(isset($_REQUEST['book'])){ - try{ + if (isset($_REQUEST['book'])) { + try { $book = $_REQUEST['book']; - $book = str_replace('..','',$book); // no upper dirs, lowers might be supported later - $epub = new EPub($bookdir.$book.'.epub'); - }catch (Exception $e){ + $book = str_replace('..', '', $book); // no upper dirs, lowers might be supported later + $epub = new EPub($bookdir . $book . '.epub'); + } catch (Exception $e) { $error = $e->getMessage(); } } // return image data - if(isset($_REQUEST['img']) && isset($epub)){ - $img = $epub->Cover(); - header('Content-Type: '.$img['mime']); - echo $img['data']; + if (isset($_REQUEST['img']) && isset($epub)) { + $img = $epub->getCoverFile(); + header('Content-Type: ' . $img['mime']); + echo $epub->getFile($img['path']); exit; } // save epub data - if($_REQUEST['save'] && isset($epub)){ + if (!empty($_REQUEST['save']) && isset($epub)) { $epub->Title($_POST['title']); $epub->Description($_POST['description']); $epub->Language($_POST['language']); $epub->Publisher($_POST['publisher']); $epub->Copyright($_POST['copyright']); - $epub->ISBN($_POST['isbn']); + $epub->Identifier(EPub::IDENT_ISBN); $epub->Subjects($_POST['subjects']); $authors = array(); - foreach((array) $_POST['authorname'] as $num => $name){ - if($name){ + foreach ((array)$_POST['authorname'] as $num => $name) { + if ($name) { $as = $_POST['authoras'][$num]; - if(!$as) $as = $name; + if (!$as) { + $as = $name; + } $authors[$as] = $name; } } @@ -56,46 +56,50 @@ // handle image $cover = ''; - if(preg_match('/^https?:\/\//i',$_POST['coverurl'])){ + if (preg_match('/^https?:\/\//i', $_POST['coverurl'])) { $data = @file_get_contents($_POST['coverurl']); - if($data){ + if ($data) { $cover = tempnam(sys_get_temp_dir(), 'epubcover'); - file_put_contents($cover,$data); + file_put_contents($cover, $data); unset($data); } - }elseif(is_uploaded_file($_FILES['coverfile']['tmp_name'])){ + } elseif(is_uploaded_file($_FILES['coverfile']['tmp_name'])) { $cover = $_FILES['coverfile']['tmp_name']; } - if($cover){ + if ($cover) { $info = @getimagesize($cover); - if(preg_match('/^image\/(gif|jpe?g|png)$/',$info['mime'])){ - $epub->Cover($cover,$info['meta']); - }else{ - $error = "Not a valid image file".$cover; + if (preg_match('/^image\/(gif|jpe?g|png)$/', $info['mime'])) { + $epub->setCoverFile($cover, $info['mime']); + } else { + $error = 'Not a valid image file' . $cover; } } // save the ebook - try{ + try { $epub->save(); - }catch(Exception $e){ + } catch (Exception $e) { $error = $e->getMessage(); } // clean up temporary cover file - if($cover) @unlink($cover); + if ($cover) { + @unlink($cover); + } // rename $author = array_shift(array_keys($epub->Authors())); $title = $epub->Title(); - $new = to_file($author.'-'.$title); - $new = $bookdir.$new.'.epub'; - $old = $epub->file(); - if(realpath($new) != realpath($old)){ - if(!@rename($old,$new)) $new = $old; //rename failed, stay here + $new = to_file($author . '-' . $title); + $new = $bookdir . $new . '.epub'; + $old = $epub->getEPubLocation(); + if (realpath($new) != realpath($old)) { + if (!@rename($old, $new)) { + $new = $old; //rename failed, stay here + } } - $go = basename($new,'.epub'); - header('Location: ?book='.rawurlencode($go)); + $go = basename($new, '.epub'); + header('Location: ?book=' . rawurlencode($go)); exit; } @@ -110,7 +114,7 @@ <link rel="stylesheet" type="text/css" href="assets/css/style.css" /> <script type="text/javascript"> - <?php if($error) echo "alert('".htmlspecialchars($error)."');";?> + <?php if(isset($error)) echo "alert('" . htmlspecialchars($error) . "');";?> </script> </head> <body> @@ -118,18 +122,18 @@ <div id="wrapper"> <ul id="booklist"> <?php - $list = glob($bookdir.'/*.epub'); - foreach($list as $book){ - $base = basename($book,'.epub'); + $list = glob($bookdir . '/*.epub'); + foreach ($list as $book) { + $base = basename($book, '.epub'); $name = book_output($base); - echo '<li '.($base == $_REQUEST['book'] ? 'class="active"' : '' ).'>'; - echo '<a href="?book='.htmlspecialchars($base).'">'.$name.'</a>'; + echo '<li ' . (isset($_REQUEST['book']) && $base == $_REQUEST['book'] ? 'class="active"' : '' ) . '>'; + echo '<a href="?book=' . htmlspecialchars($base) . '">' . $name . '</a>'; echo '</li>'; } ?> </ul> - <?php if($epub): ?> + <?php if(isset($epub)): ?> <form action="" method="post" id="bookpanel" enctype="multipart/form-data"> <input type="hidden" name="book" value="<?php echo htmlspecialchars($_REQUEST['book'])?>" /> @@ -143,7 +147,7 @@ <td id="authors"> <?php $count = 0; - foreach($epub->Authors() as $as => $name){ + foreach ($epub->Authors() as $as => $name) { ?> <p> <input type="text" name="authorname[<?php echo $count?>]" value="<?php echo htmlspecialchars($name)?>" /> @@ -158,13 +162,13 @@ <tr> <th>Description<br /> <img src="?book=<?php echo htmlspecialchars($_REQUEST['book'])?>&img=1" id="cover" width="90" - class="<?php $c = $epub->Cover(); echo ($c['found']?'hasimg':'noimg')?>" /> + class="<?php $c = $epub->getCoverFile(); echo ($c?'hasimg':'noimg')?>" /> </th> <td><textarea name="description"><?php echo htmlspecialchars($epub->Description())?></textarea></td> </tr> <tr> <th>Subjects</th> - <td><input type="text" name="subjects" value="<?php echo htmlspecialchars(join(', ',$epub->Subjects()))?>" /></td> + <td><input type="text" name="subjects" value="<?php echo htmlspecialchars(join(', ', $epub->Subjects()))?>" /></td> </tr> <tr> <th>Publisher</th> @@ -180,7 +184,7 @@ class="<?php $c = $epub->Cover(); echo ($c['found']?'hasimg':'noimg')?>" /> </tr> <tr> <th>ISBN</th> - <td><p><input type="text" name="isbn" value="<?php echo htmlspecialchars($epub->ISBN())?>" /></p></td> + <td><p><input type="text" name="isbn" value="<?php echo htmlspecialchars($epub->Identifier(EPub::IDENT_ISBN))?>" /></p></td> </tr> <tr> <th>Cover Image</th> @@ -198,7 +202,7 @@ class="<?php $c = $epub->Cover(); echo ($c['found']?'hasimg':'noimg')?>" /> <p>View and edit epub books stored in <code><?php echo htmlspecialchars($bookdir)?></code>.</p> <div class="license"> - <p><?php echo str_replace("\n\n",'</p><p>',htmlspecialchars(file_get_contents('LICENSE'))) ?></p> + <p><?php echo str_replace("\n\n", '</p><p>', htmlspecialchars(file_get_contents('LICENSE'))) ?></p> </div> <?php endif; ?> diff --git a/lib/EPub.php b/lib/EPub.php new file mode 100644 index 0000000..fd40b68 --- /dev/null +++ b/lib/EPub.php @@ -0,0 +1,772 @@ +<?php +/** + * PHP EPub Meta library + * + * @author Andreas Gohr <andi@splitbrain.org> + * @author Sébastien Lucas <sebastien@slucas.fr> + */ + +namespace splitbrain\epubmeta; + +define('METADATA_FILE', 'META-INF/container.xml'); + +class EPub +{ + #region Constants + const DATE_MODIFICATION = 'modification'; + const DATE_CREATION = 'creation'; + const DATE_CONVERSION = 'conversion'; + const DATE_PUB = 'publication'; + const DATE_PUB_ORIG = 'original-publication'; + + const IDENT_URI = 'URI'; + const IDENT_URN = 'URN'; + const IDENT_ISBN = 'ISBN'; + const IDENT_AMAZON = 'AMAZON'; + const IDENT_GOOGLE = 'GOOGLE'; + const IDENT_CALIBRE = 'CALIBRE'; + #endregion + + /** @var string Location of the meta package within the epub */ + protected $meta; + /** @var EPubDOMDocument Parsed XML of the meta package */ + public $meta_xml; + /** @var EPubDOMXPath XPath access to the meta package */ + protected $meta_xpath; + /** @var string The path to the epub file */ + protected $file; + /** @var \clsTbsZip handles the ZIP operations on the epub file */ + protected $zip; + /** @var null|array The manifest data, eg. which files are available */ + protected $manifest = null; + + /** + * Constructor + * + * @param string $file path to epub file to work on + * @throws \Exception if metadata could not be loaded + */ + public function __construct($file) + { + // open file + $this->file = $file; + $this->zip = new \clsTbsZip(); + if (!$this->zip->Open($this->file)) { + throw new \Exception('Failed to read epub file'); + } + + // read container data + if (!$this->zip->FileExists(METADATA_FILE)) { + throw new \Exception('Unable to find metadata.xml'); + } + + $data = $this->zip->FileRead(METADATA_FILE); + if ($data == false) { + throw new \Exception('Failed to access epub container data'); + } + $xml = new EPubDOMDocument(); + $xml->loadXML($data); + $xpath = new EPubDOMXPath($xml); + $nodes = $xpath->query('//n:rootfiles/n:rootfile[@media-type="application/oebps-package+xml"]'); + $this->meta = $nodes->item(0)->attr('full-path'); + + // load metadata + if (!$this->zip->FileExists($this->meta)) { + throw new \Exception('Unable to find ' . $this->meta); + } + + $data = $this->zip->FileRead($this->meta); + if (!$data) { + throw new \Exception('Failed to access epub metadata'); + } + $this->meta_xml = new EpubDOMDocument(); + $this->meta_xml->loadXML($data); + $this->meta_xml->formatOutput = true; + $this->meta_xpath = new EPubDOMXPath($this->meta_xml); + } + + /** + * Lists all files from the manifest + * + * @return array + */ + protected function readManifest() + { + $manifest = array(); + $nodes = $this->meta_xpath->query('//opf:manifest/opf:item'); + + foreach ($nodes as $node) { + /** @var EPubDOMElement $node */ + $file = $node->attr('opf:href'); + if ($file === '') { + continue; + } + $file = $this->getFullPath($file); + + $manifest[$file] = array( + 'id' => $node->attr('id'), + 'mime' => $node->attr('opf:media-type'), + 'exists' => (bool)$this->zip->FileExists($file), + 'path' => $file, + ); + } + + return $manifest; + } + + /** + * Close the epub file + */ + public function close() + { + $this->zip->Close(); + } + + /** + * Writes back all changes to the epub + */ + public function save() + { + $data = $this->download(); + $this->zip->Close(); + file_put_contents($this->getEPubLocation(), $data); + } + + /** + * Get the updated epub + * + * @param null|string $file + * @return string|bool + */ + public function download($file = null) + { + $this->zip->FileReplace($this->meta, $this->meta_xml->saveXML()); + + if ($file) { + return $this->zip->Flush(TBSZIP_DOWNLOAD, $file); + } else { + $this->zip->Flush(TBSZIP_STRING); + /** @noinspection PhpUndefinedFieldInspection */ + return $this->zip->OutputSrc; // ugly but currently the only interface in the ZIP lib + } + } + + /** + * Remove iTunes files + */ + public function cleanITunesCrap() + { + if ($this->zip->FileExists('iTunesMetadata.plist')) { + $this->zip->FileReplace('iTunesMetadata.plist', false); + } + if ($this->zip->FileExists('iTunesArtwork')) { + $this->zip->FileReplace('iTunesArtwork', false); + } + } + + /** + * Makes sure the epub3 cover-image attribute is set + */ + public function updateForKepub() + { + $cover = $this->getCoverFile(); + if ($cover === null) { + return; + } + + $id = $cover['id']; + $nodes = $this->meta_xpath->query('//opf:manifest/opf:item[@id="' . $id . '"]'); + $node = $nodes->item(0); + if (!$node) { + return; + } + $node->attr('opf:properties', 'cover-image'); + } + + + #region Book Attribute Getter/Setters + + /** + * Get or set the book author(s) + * + * Authors should be given with a "file-as" and a real name. The file as + * is used for sorting in e-readers. + * + * Example: + * + * array( + * 'Pratchett, Terry' => 'Terry Pratchett', + * 'Simpson, Jacqueline' => 'Jacqueline Simpson', + * ) + * + * When a string is given, it assumed to be a comma separted list of Author names + * + * @param array|string|null $authors + * @return array + */ + public function Authors($authors = null) + { + // set new data + if ($authors !== null) { + // Author where given as a comma separated list + if (is_string($authors)) { + if ($authors == '') { + $authors = array(); + } else { + $authors = explode(',', $authors); + $authors = array_map('trim', $authors); + } + } + + // delete existing nodes + $nodes = $this->meta_xpath->query('//opf:metadata/dc:creator[@opf:role="aut"]'); + foreach ($nodes as $node) { + /** @var EPubDOMElement $node */ + $node->delete(); + } + + // add new nodes + $parent = $this->meta_xpath->query('//opf:metadata')->item(0); + foreach ($authors as $as => $name) { + if (is_int($as)) { + $as = $name; //numeric array given + } + $node = $parent->newChild('dc:creator', $name); + $node->attr('opf:role', 'aut'); + $node->attr('opf:file-as', $as); + } + + $this->reparse(); + } + + // read current data + $rolefix = false; + $authors = array(); + $nodes = $this->meta_xpath->query('//opf:metadata/dc:creator[@opf:role="aut"]'); + if ($nodes->length == 0) { + // no nodes where found, let's try again without role + $nodes = $this->meta_xpath->query('//opf:metadata/dc:creator'); + $rolefix = true; + } + foreach ($nodes as $node) { + $name = $node->nodeValue; + $as = $node->attr('opf:file-as'); + if (!$as) { + $as = $name; + $node->attr('opf:file-as', $as); + } + if ($rolefix) { + $node->attr('opf:role', 'aut'); + } + $authors[$as] = $name; + } + return $authors; + } + + /** + * Set or get the book title + * + * @param string|null $title + * @return string + */ + public function Title($title = null) + { + return $this->getset('dc:title', $title); + } + + /** + * Set or get the book's language + * + * @param string|null $lang + * @return string + */ + public function Language($lang = null) + { + return $this->getset('dc:language', $lang); + } + + /** + * Set or get the book' publisher info + * + * @param string|null $publisher + * @return string + */ + public function Publisher($publisher = null) + { + return $this->getset('dc:publisher', $publisher); + } + + /** + * Set or get the book's copyright info + * + * @param string|null $rights + * @return string + */ + public function Copyright($rights = null) + { + return $this->getset('dc:rights', $rights); + } + + /** + * Set or get the book's description + * + * @param string|null $description + * @return string + */ + public function Description($description = null) + { + return $this->getset('dc:description', $description); + } + + /** + * Set or get the book's Unique Identifier + * + * @param string|null $uuid Unique identifier + * @return string + * @throws \Exception + * @todo auto add unique identifer if needed? + */ + public function Uuid($uuid = null) + { + $nodes = $this->meta_xpath->query('/opf:package'); + if ($nodes->length !== 1) { + throw new \Exception('Cannot find ebook identifier'); + } + $identifier = $nodes->item(0)->attr('unique-identifier'); + + $res = $this->getset('dc:identifier', $uuid, 'id', $identifier); + + return $res; + } + + /** + * Read or set a date + * + * @param string $type The type to set/read - use the DATE_* constants for typical dates + * @param string|null $date Date eg: 2012-05-19T12:54:25Z + * @return string + */ + public function Date($type, $date = null) + { + return $this->getset('dc:date', $date, 'opf:event', $type); + } + + /** + * Set or get the book's Identifier + * + * @param string $type Type of identifier, use TYPE_* constants for typical + * @param string|null $ident Identifier + * @return string + */ + public function Identifier($type, $ident = null) + { + $res = $this->getset('dc:identifier', $ident, 'opf:scheme', $type); + + return $res; + } + + /** + * Set or get the Series of the book + * + * @param string|null $series + * @return string + */ + public function Series($series = null) + { + return $this->getset('opf:meta', $series, 'name', 'calibre:series', 'content'); + } + + /** + * Set or get the Series Index of the book + * + * @param string|null $seriesIndex + * @return string + */ + public function SeriesIndex($seriesIndex = null) + { + return $this->getset('opf:meta', $seriesIndex, 'name', 'calibre:series_index', 'content'); + } + + /** + * Set or get the book's subjects (aka. tags) + * + * Subject should be given as array, but a comma separated string will also + * be accepted. + * + * @param array|string|null $subjects + * @return string[] + */ + public function Subjects($subjects = null) + { + // setter + if ($subjects !== null) { + if (is_string($subjects)) { + if ($subjects === '') { + $subjects = array(); + } else { + $subjects = explode(',', $subjects); + $subjects = array_map('trim', $subjects); + } + } + + // delete previous + $nodes = $this->meta_xpath->query('//opf:metadata/dc:subject'); + foreach ($nodes as $node) { + /** @var EPubDOMElement $node */ + $node->delete(); + } + // add new ones + $parent = $this->meta_xpath->query('//opf:metadata')->item(0); + foreach ($subjects as $subj) { + $node = $this->meta_xml->createElement('dc:subject', htmlspecialchars($subj)); + $parent->appendChild($node); + } + + $this->reparse(); + } + + //getter + $subjects = array(); + $nodes = $this->meta_xpath->query('//opf:metadata/dc:subject'); + foreach ($nodes as $node) { + $subjects[] = $node->nodeValue; + } + return $subjects; + } + + #endregion + + #region Book Attribute Getters + + /** + * The path to the currently loaded EPub + */ + public function getEPubLocation() + { + return $this->file; + } + + /** + * Returns the Table of Contents + * + * @return array + * @throws \Exception + */ + public function getToc() + { + $contents = array(); + + // find TOC file + $tocid = $this->meta_xpath->query('//opf:spine')->item(0)->attr('toc'); + $tochref = $this->meta_xpath->query('//opf:manifest/opf:item[@id="' . $tocid . '"]')->item(0)->attr('href'); + $tocpath = $this->getFullPath($tochref); + // read TOC file + if (!$this->zip->FileExists($tocpath)) { + throw new \Exception('Unable to find ' . $tocpath); + } + $data = $this->zip->FileRead($tocpath); + // parse TOC file + $toc_xml = new EPubDOMDocument(); + $toc_xml->loadXML($data); + $toc_xpath = new EPubDOMXPath($toc_xml); + + // read nav point nodes + $nodes = $toc_xpath->query('//ncx:ncx/ncx:navMap/ncx:navPoint'); + foreach ($nodes as $node) { + $contents[] = $this->mkTocEntry($node, $toc_xpath); + + $insidenodes = $toc_xpath->query('ncx:navPoint', $node); + foreach ($insidenodes as $insidenode) { + $contents[] = $this->mkTocEntry($insidenode, $toc_xpath); + } + } + + return $contents; + } + + /** + * Returns info about the given file from the manifest + * + * @param $path + * @return array + */ + public function getFileInfo($path) + { + if ($this->manifest === null) { + $this->manifest = $this->readManifest(); + } + + if (isset($this->manifest[$path])) { + return $this->manifest[$path]; + } + return array('id' => '', 'mime' => '', 'exists' => false, 'path' => $path); + } + + /** + * Get info on the cover image if any + * + * Returns the same info as getFileInfo() for the cover image if any. Returns null + * if there's no cover image. + * + * @return array|null + */ + public function getCoverFile() + { + $nodes = $this->meta_xpath->query('//opf:metadata/opf:meta[@name="cover"]'); + if (!$nodes->length) { + return null; + } + + $coverid = (String)$nodes->item(0)->attr('opf:content'); + if (!$coverid) { + return null; + } + + $nodes = $this->meta_xpath->query('//opf:manifest/opf:item[@id="' . $coverid . '"]'); + if (!$nodes->length) { + return null; + } + + return $this->getFileInfo($this->getFullPath($nodes->item(0)->attr('href'))); + } + + /** + * Removes the cover image if there's one + * + * If the actual image file was added by this library it will be removed. Otherwise only the + * reference to it is removed from the metadata, since the same image might be referenced + * by other parts of the epub file. + */ + public function clearCover() + { + // do nothing if there's no cover currently + $cover = $this->getCoverFile(); + if ($cover === null) { + return; + } + + // remove current pointer + $nodes = $this->meta_xpath->query('//opf:metadata/opf:meta[@name="cover"]'); + foreach ($nodes as $node) { + /** @var EPubDOMElement $node */ + $node->delete(); + } + // remove previous manifest entries if they where made by us + $nodes = $this->meta_xpath->query('//opf:manifest/opf:item[@id="php-epub-meta-cover"]'); + foreach ($nodes as $node) { + /** @var EPubDOMElement $node */ + $node->delete(); + } + // remove the actual file if it was added by us + if ($cover['id'] == 'php-epub-meta-cover') { + $this->zip->FileReplace($cover['path'], false); + $this->manifest = null; + } + } + + /** + * Set a new cover image + * + * @param string $path path to the image to set on the local file system + * @param string $mime mime type of that image (like 'image/jpeg') + * @throws \Exception when the given image can't be read + */ + public function setCoverFile($path, $mime) + { + $this->clearCover(); + $data = file_get_contents($path); + if ($data === false) { + throw new \Exception("Couldn't load data from $path"); + } + + /** @var EPubDOMElement $node */ + + // add pointer + $parent = $this->meta_xpath->query('//opf:metadata')->item(0); + $node = $parent->newChild('opf:meta'); + $node->attr('opf:name', 'cover'); + $node->attr('opf:content', 'php-epub-meta-cover'); + + // add manifest + $parent = $this->meta_xpath->query('//opf:manifest')->item(0); + $node = $parent->newChild('opf:item'); + $node->attr('id', 'php-epub-meta-cover'); + $node->attr('opf:href', 'php-epub-meta-cover.img'); + $node->attr('opf:media-type', $mime); + $node->attr('opf:properties', 'cover-image'); + + $full = $this->getFullPath('php-epub-meta-cover.img'); + + // remember path for save action + if ($this->zip->FileExists($full)) { + $this->zip->FileReplace($full, $data); + } else { + $this->zip->FileAdd($full, $data); + } + + $this->reparse(); + } + + /** + * Read the contents of a file witin the epub + * + * You probably want to use getFileInfo() first to check if the file exists and get + * additional file info like the mime type + * + * @param string $path the path within the epub file + * @return string the raw file contents + * @throws \Exception when the file doesn't exists + */ + public function getFile($path) + { + if (!$this->zip->FileExists($path)) { + throw new \Exception('No such file'); + } + + return $this->zip->FileRead($path); + } + + #endregion + + #region Internal Functions + + /** + * Enhances the a single TOC entry with data from the manifest + * + * @param EPubDOMElement $node an ncx:navPoint entry + * @param EPubDOMXPath $toc_xpath + * @return array + */ + protected function mkTocEntry(EPubDOMElement $node, EPubDOMXPath $toc_xpath) + { + $title = $toc_xpath->query('ncx:navLabel/ncx:text', $node)->item(0)->nodeValue; + $src = $toc_xpath->query('ncx:content', $node)->item(0)->attr('src'); + + $file = $this->getFullPath($src); + + return array_merge(array('title' => $title, 'src' => $src), $this->getFileInfo($file)); + } + + #endregion + + /** + * Resolves paths relative to the meta container location + * + * @param string $file relative path + * @return string full path within the zip + */ + private function getFullPath($file) + { + list($file) = explode('#', $file); // strip anchors + $path = dirname('/' . $this->meta) . '/' . $file; + $path = ltrim($path, '\\'); + $path = ltrim($path, '/'); + + return $path; + } + + /** + * A simple getter/setter for simple meta attributes + * + * It should only be used for attributes that are expected to be unique + * + * @param string $item XML node to set/get + * @param string|null $value New value to set, null to get, passing an empty string deletes the node + * @param string|null $att Attribute name the node needs to have for a match + * @param string|null $aval Attribute value the node needs to have for a match + * @param string|null $datt Destination attribute to set instead of the node value + * @return string + */ + protected function getset($item, $value = null, $att = null, $aval = null, $datt = null) + { + // construct xpath + $xpath = '//opf:metadata/' . $item; + if ($att) { + if ($aval) { + $xpath .= '[@' . $att . '="' . $aval . '"]'; + } else { + $xpath .= '[@' . $att . ']'; + } + } + + // set value + if ($value !== null) { + $value = htmlspecialchars($value); + $nodes = $this->meta_xpath->query($xpath); + if ($nodes->length == 1) { + if ($value === '') { + // the user want's to empty this value -> delete the node + $nodes->item(0)->delete(); + } else { + // replace value + if ($datt) { + $nodes->item(0)->attr($datt, $value); + } else { + $nodes->item(0)->nodeValue = $value; + } + } + } else { + // if there are multiple matching nodes for some reason delete + // them. we'll replace them all with our own single one + foreach ($nodes as $n) { + /** @var EPubDOMElement $n */ + $n->delete(); + } + // readd them + if ($value) { + $parent = $this->meta_xpath->query('//opf:metadata')->item(0); + + $node = $parent->newChild($item); + /** @var EPubDOMElement $node */ + if ($att) { + $node->attr($att, $aval); + } + if ($datt) { + $node->attr($datt, $value); + } else { + $node->nodeValue = $value; + } + } + } + + $this->reparse(); + } + + // get value + $nodes = $this->meta_xpath->query($xpath); + if ($nodes->length) { + if ($datt) { + return $nodes->item(0)->attr($datt); + } else { + return $nodes->item(0)->nodeValue; + } + } else { + return ''; + } + } + + /** + * Return a not found response for Cover() + */ + protected function no_cover() + { + return array( + 'data' => base64_decode('R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAEALAAAAAABAAEAAAIBTAA7'), + 'mime' => 'image/gif', + 'found' => false + ); + } + + /** + * Reparse the DOM tree + * + * This is needed when new nodes are added to the DOM, otherwise XPath won't find those new + * nodes. + */ + protected function reparse() + { + $this->meta_xml->loadXML($this->meta_xml->saveXML()); + $this->meta_xpath = new EPubDOMXPath($this->meta_xml); + $this->manifest = null; + } +} diff --git a/lib/EPubDOMDocument.php b/lib/EPubDOMDocument.php new file mode 100644 index 0000000..a65c58c --- /dev/null +++ b/lib/EPubDOMDocument.php @@ -0,0 +1,23 @@ +<?php + +namespace splitbrain\epubmeta; + +class EpubDOMDocument extends \DOMDocument +{ + + /** @var EPubDOMElement documentElement */ + public $documentElement; + + /** + * Creates a new DOMDocument object + * @link http://php.net/manual/domdocument.construct.php + * @param $version [optional] The version number of the document as part of the XML declaration. + * @param $encoding [optional] The encoding of the document as part of the XML declaration. + */ + public function __construct($version = '', $encoding = '') + { + parent::__construct($version, $encoding); + $this->registerNodeClass('DOMElement', '\\splitbrain\\epubmeta\\EPubDOMElement'); + } + +} \ No newline at end of file diff --git a/lib/EPubDOMElement.php b/lib/EPubDOMElement.php new file mode 100644 index 0000000..6209a40 --- /dev/null +++ b/lib/EPubDOMElement.php @@ -0,0 +1,125 @@ +<?php +/** + * PHP EPub Meta library + * + * @author Andreas Gohr <andi@splitbrain.org> + * @author Sébastien Lucas <sebastien@slucas.fr> + */ + +namespace splitbrain\epubmeta; + +class EPubDOMElement extends \DOMElement +{ + static public $namespaces = array( + 'n' => 'urn:oasis:names:tc:opendocument:xmlns:container', + 'opf' => 'http://www.idpf.org/2007/opf', + 'dc' => 'http://purl.org/dc/elements/1.1/', + 'ncx' => 'http://www.daisy.org/z3986/2005/ncx/' + ); + + public function __construct($name, $value = '', $namespaceURI = '') + { + list($ns, $name) = $this->splitns($name); + $value = htmlspecialchars($value); + if (!$namespaceURI && $ns) { + $namespaceURI = self::$namespaces[$ns]; + } + parent::__construct($name, $value, $namespaceURI); + } + + /** + * Create and append a new child + * + * Works with our epub namespaces and omits default namespaces + * @param string $name + * @param string $value + * @return \DOMNode + */ + public function newChild($name, $value = '') + { + $nsuri = ''; + list($ns, $local) = $this->splitns($name); + if ($ns) { + $nsuri = self::$namespaces[$ns]; + if ($this->isDefaultNamespace($nsuri)) { + $name = $local; + $nsuri = ''; + } + } + + // this doesn't call the construcor: $node = $this->ownerDocument->createElement($name,$value); + $node = new EPubDOMElement($name, $value, $nsuri); + return $this->appendChild($node); + } + + /** + * Split given name in namespace prefix and local part + * + * @param string $name + * @return array (namespace, name) + */ + public function splitns($name) + { + $list = explode(':', $name, 2); + if (count($list) < 2) { + array_unshift($list, ''); + } + return $list; + } + + /** + * Simple EPub namespace aware attribute accessor + * @param string $attr Attribute to access + * @param null $value Value to set. False to delete + * @return string + */ + public function attr($attr, $value = null) + { + list($ns, $attr) = $this->splitns($attr); + + $nsuri = ''; + if ($ns) { + $nsuri = self::$namespaces[$ns]; + if (!$this->namespaceURI) { + if ($this->isDefaultNamespace($nsuri)) { + $nsuri = ''; + } + } elseif ($this->namespaceURI == $nsuri) { + $nsuri = ''; + } + } + + if (!is_null($value)) { + if ($value === false) { + // delete if false was given + if ($nsuri) { + $this->removeAttributeNS($nsuri, $attr); + } else { + $this->removeAttribute($attr); + } + } else { + // modify if value was given + if ($nsuri) { + $this->setAttributeNS($nsuri, $attr, $value); + } else { + $this->setAttribute($attr, $value); + } + } + } else { + // return value if none was given + if ($nsuri) { + return $this->getAttributeNS($nsuri, $attr); + } else { + return $this->getAttribute($attr); + } + } + } + + /** + * Remove this node from the DOM + */ + public function delete() + { + $this->parentNode->removeChild($this); + } +} diff --git a/lib/EPubDOMNodeList.php b/lib/EPubDOMNodeList.php new file mode 100644 index 0000000..eda3a40 --- /dev/null +++ b/lib/EPubDOMNodeList.php @@ -0,0 +1,17 @@ +<?php + +namespace splitbrain\epubmeta; + +class EPubDOMNodeList extends \DOMNodeList +{ + /** + * @link http://php.net/manual/en/domnodelist.item.php + * @param int $index + * @return EPubDOMElement + */ + public function item($index) + { + return parent::item($index); + } + +} \ No newline at end of file diff --git a/lib/EPubDOMXPath.php b/lib/EPubDOMXPath.php new file mode 100644 index 0000000..f61989f --- /dev/null +++ b/lib/EPubDOMXPath.php @@ -0,0 +1,34 @@ +<?php +/** + * PHP EPub Meta library + * + * @author Andreas Gohr <andi@splitbrain.org> + * @author Sébastien Lucas <sebastien@slucas.fr> + */ + +namespace splitbrain\epubmeta; + +class EPubDOMXPath extends \DOMXPath +{ + public function __construct(EpubDOMDocument $doc) + { + parent::__construct($doc); + + foreach (EPubDOMElement::$namespaces as $ns => $url) { + $this->registerNamespace($ns, $url); + } + } + + /** + * Evaluates the given XPath expression + * @link http://php.net/manual/en/domxpath.query.php + * @param string $expression + * @param \DOMNode $contextnode + * @return EpubDOMNodeList + */ + public function query($expression, $contextnode = null) + { + return parent::query($expression, $contextnode); + } + +} diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..115f946 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<phpunit + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.5/phpunit.xsd" + bootstrap="./vendor/autoload.php" + verbose="true" + colors="true"> + <filter> + <whitelist processUncoveredFilesFromWhitelist="false"> + <!-- this is the path of the files included in your clover report --> + <directory suffix=".php">./</directory> + <directory suffix=".php">./lib/</directory> + <exclude> + <directory suffix=".php">./test</directory> + <directory suffix=".php">./vendor</directory> + <file>tbszip.php</file> + </exclude> + </whitelist> + </filter> + <logging> + <log type="coverage-clover" target="./clover.xml"/> + <log type="coverage-text" target="php://stdout" showUncoveredFiles="false"/> + </logging> + <testsuites> + <testsuite name="php-epub-meta"> + <directory>./test/</directory> + </testsuite> + </testsuites> +</phpunit> diff --git a/test/EPub.php b/test/EPub.php new file mode 100644 index 0000000..618f6b3 --- /dev/null +++ b/test/EPub.php @@ -0,0 +1,20 @@ +<?php + +namespace splitbrain\epubmeta\test; + +/** + * Class EPub + * + * Gives access to protected methods for testing + * + * @package splitbrain\epubmeta\test + */ +class EPub extends \splitbrain\epubmeta\EPub +{ + + public function readManifest() + { + return parent::readManifest(); + } + +} \ No newline at end of file diff --git a/test/epubTest.php b/test/epubTest.php new file mode 100644 index 0000000..cdb703e --- /dev/null +++ b/test/epubTest.php @@ -0,0 +1,344 @@ +<?php + +namespace splitbrain\epubmeta\test; + +use splitbrain\epubmeta\test\EPub; + +class EPubTest extends \PHPUnit_Framework_TestCase +{ + /** @var EPub */ + protected $epub; + + protected function setUp() + { + // sometime I might have accidentally broken the test file + if (filesize(realpath(__DIR__) . '/test.epub') != 768780) { + die('test.epub has wrong size, make sure it\'s unmodified'); + } + + // we work on a copy to test saving + if (!copy(realpath(__DIR__) . '/test.epub', realpath(__DIR__) . '/test.copy.epub')) { + die('failed to create copy of the test book'); + } + + $this->epub = new EPub(realpath(__DIR__) . '/test.copy.epub'); + } + + public static function tearDownAfterClass() + { + unlink(realpath(dirname(__FILE__)) . '/test.copy.epub'); + } + + public function testManifest() + { + $manifest = $this->epub->readManifest(); + + $this->assertEquals(41, count($manifest)); + $this->assertArrayHasKey('OPS/css/page.css', $manifest); + $this->assertEquals( + array( + 'id' => 'page-css', + 'mime' => 'text/css', + 'exists' => true, + 'path' => 'OPS/css/page.css' + ), + $manifest['OPS/css/page.css'] + ); + } + + public function testToc() + { + $toc = $this->epub->getToc(); + + $this->assertEquals(34, count($toc)); + $this->assertEquals( + array( + 'title' => 'Prologue', + 'src' => 'main0.xml#section_77304', + 'id' => 'main0', + 'mime' => 'application/xhtml+xml', + 'exists' => true, + 'path' => 'OPS/main0.xml' + ) + , $toc[3]); + } + + public function testFileReading() + { + $expect = '@import "page.css"; + +body {padding: 0;} +div.aboutauthor {text-align: left;} + +div.also { + text-align: left; + padding-top: 5%;} + +a { + color: #000000; + text-decoration: none;} + +p { + margin-top: 0.0em; + margin-bottom: 0.0em; + text-indent: 1.0em; + text-align: justify;}'; + + $data = $this->epub->getFile('OPS/css/about.css'); + + $this->assertEquals($expect, $data); + } + + /** + * @expectedException \Exception + */ + public function testFileFailed() + { + $this->epub->getFile('does/not/exist'); + } + + public function testAuthors() + { + // read curent value + $this->assertEquals( + array('Shakespeare, William' => 'William Shakespeare'), + $this->epub->Authors() + ); + + // remove value with string + $this->assertEquals( + array(), + $this->epub->Authors('') + ); + + // set single value by String + + $this->assertEquals( + array('John Doe' => 'John Doe'), + $this->epub->Authors('John Doe') + ); + + // set single value by indexed array + $this->assertEquals( + array('John Doe' => 'John Doe'), + $this->epub->Authors(array('John Doe')) + ); + + // remove value with array + $this->assertEquals( + array(), + $this->epub->Authors(array()) + ); + + // set single value by associative array + $this->assertEquals( + array('Doe, John' => 'John Doe'), + $this->epub->Authors(array('Doe, John' => 'John Doe')) + ); + + // set multi value by string + $this->assertEquals( + array('John Doe' => 'John Doe', 'Jane Smith' => 'Jane Smith'), + $this->epub->Authors('John Doe, Jane Smith') + ); + + // set multi value by indexed array + $this->assertEquals( + array('John Doe' => 'John Doe', 'Jane Smith' => 'Jane Smith'), + $this->epub->Authors(array('John Doe', 'Jane Smith')) + ); + + // set multi value by associative array + $this->assertEquals( + array('Doe, John' => 'John Doe', 'Smith, Jane' => 'Jane Smith'), + $this->epub->Authors(array('Doe, John' => 'John Doe', 'Smith, Jane' => 'Jane Smith')) + ); + + // check escaping + $this->assertEquals( + array('Doe, John ' => 'John Doe '), + $this->epub->Authors(array('Doe, John ' => 'John Doe ')) + ); + } + + public function testTitle() + { + // get current value + $this->assertEquals( + 'Romeo and Juliet', + $this->epub->Title() + ); + + // delete current value + $this->assertEquals( + '', + $this->epub->Title('') + ); + + // get current value + $this->assertEquals( + '', + $this->epub->Title() + ); + + // set new value + $this->assertEquals( + 'Foo Bar', + $this->epub->Title('Foo Bar') + ); + + // check escaping + $this->assertEquals( + 'Foo Bar', + $this->epub->Title('Foo Bar') + ); + } + + public function testSubject() + { + // get current values + $this->assertEquals( + array('Fiction', 'Drama', 'Romance'), + $this->epub->Subjects() + ); + + // delete current values with String + $this->assertEquals( + array(), + $this->epub->Subjects('') + ); + + // set new values with String + $this->assertEquals( + array('Fiction', 'Drama', 'Romance'), + $this->epub->Subjects('Fiction, Drama, Romance') + ); + + // delete current values with Array + $this->assertEquals( + array(), + $this->epub->Subjects(array()) + ); + + // set new values with array + $this->assertEquals( + array('Fiction', 'Drama', 'Romance'), + $this->epub->Subjects(array('Fiction', 'Drama', 'Romance')) + ); + + // check escaping + $this->assertEquals( + array('Fiction', 'Drama ', 'Romance'), + $this->epub->Subjects(array('Fiction', 'Drama ', 'Romance')) + ); + } + + public function testDates() + { + $this->assertEquals('1597', $this->epub->Date(EPub::DATE_PUB_ORIG)); + $this->assertEquals('2008-09-18', $this->epub->Date('ops-publication')); + } + + public function testIdentifier() + { + $this->assertEquals('http://www.feedbooks.com/book/2936', $this->epub->Identifier(EPub::IDENT_URI)); + $this->assertEquals('urn:uuid:7d38d098-4234-11e1-97b6-001cc0a62c0b', $this->epub->Identifier(EPub::IDENT_URN)); + } + + public function testGetCoverFile() + { + $expect = array( + 'id' => 'book-cover', + 'mime' => 'image/png', + 'exists' => true, + 'path' => 'OPS/images/cover.png', + ); + $cover = $this->epub->getCoverFile(); + $this->assertNotNull($cover); + $this->assertEquals($expect, $cover); + } + + public function testClearCover() + { + $this->epub->clearCover(); + $this->assertNull($this->epub->getCoverFile()); + } + + public function testSetCoverFile() + { + $this->epub->setCoverFile(__DIR__ . '/test.jpg', 'image/jpeg'); + $cover = $this->epub->getCoverFile(); + + $this->assertNotNull($cover); + + $this->assertEquals( + array( + 'id' => 'php-epub-meta-cover', + 'mime' => 'image/jpeg', + 'exists' => false, + 'path' => 'OPS/php-epub-meta-cover.img' + + ), + $cover + ); + } + + public function testCancel() + { + $this->epub->setCoverFile(__DIR__ . '/test.jpg', 'image/jpeg'); + $this->epub->Title('fooooooooooooooooooooooooooooooooooooo'); + $this->epub->close(); + + clearstatcache($this->epub->getEPubLocation()); + $this->assertEquals(768780, filesize($this->epub->getEPubLocation())); + } + + public function testSave() + { + $epubfile = $this->epub->getEPubLocation(); + + $this->epub->setCoverFile(__DIR__ . '/test.jpg', 'image/jpeg'); + $this->epub->Title('fooooooooooooooooooooooooooooooooooooo'); + $this->epub->save(); + + clearstatcache($this->epub->getEPubLocation()); + $this->assertNotEquals(768780, filesize($epubfile)); + + // reload the file + $this->epub = new EPub($epubfile); + $this->assertEquals('fooooooooooooooooooooooooooooooooooooo', $this->epub->Title()); + + $this->assertEquals(array( + 'id' => 'php-epub-meta-cover', + 'mime' => 'image/jpeg', + 'exists' => true, + 'path' => 'OPS/php-epub-meta-cover.img', + + ), $this->epub->getCoverFile()); + + $this->assertEquals( + file_get_contents(__DIR__ . '/test.jpg'), + $this->epub->getFile('OPS/php-epub-meta-cover.img') + ); + + // test cover removing + $this->epub->clearCover(); + $this->assertNull($this->epub->getCoverFile()); + // our image should be gone + $this->assertEquals(array( + 'id' => '', + 'mime' => '', + 'exists' => false, + 'path' => 'OPS/php-epub-meta-cover.img', + ), $this->epub->getFileInfo('OPS/php-epub-meta-cover.img')); + // the original image should still be there, even though it's not registered as cover anymore + $this->assertEquals(array( + 'id' => 'book-cover', + 'mime' => 'image/png', + 'exists' => true, + 'path' => 'OPS/images/cover.png', + ), $this->epub->getFileInfo('OPS/images/cover.png')); + + } + +} diff --git a/test/publishCoverage.sh b/test/publishCoverage.sh new file mode 100644 index 0000000..463f7ed --- /dev/null +++ b/test/publishCoverage.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +PHP_VERSION=`php -v|grep --only-matching --perl-regexp "PHP 5\.\\d+"` +echo $PHP_VERSION + + +if [[ $PHP_VERSION != "PHP 5.4" ]] + then + echo "Bad PHP version" + exit +fi + +echo "Good PHP version" + +# Handle scrutinizer +wget https://scrutinizer-ci.com/ocular.phar +php ocular.phar code-coverage:upload --format=php-clover clover.xml diff --git a/test/test.phpunit.php b/test/test.phpunit.php deleted file mode 100644 index 88a9aa9..0000000 --- a/test/test.phpunit.php +++ /dev/null @@ -1,190 +0,0 @@ -<?php - -require '../epub.php'; - - -class EPubTest extends PHPUnit_Framework_TestCase { - - protected $epub; - - protected function setUp(){ - // sometime I might have accidentally broken the test file - if(filesize('test.epub') != 768780){ - die('test.epub has wrong size, make sure it\'s unmodified'); - } - - // we work on a copy to test saving - if(!copy('test.epub','test.copy.epub')){ - die('failed to create copy of the test book'); - } - - $this->epub = new EPub('test.copy.epub'); - } - - protected function tearDown(){ - unlink('test.copy.epub'); - } - - public function testAuthors(){ - // read curent value - $this->assertEquals( - $this->epub->Authors(), - array('Shakespeare, William' => 'William Shakespeare') - ); - - // remove value with string - $this->assertEquals( - $this->epub->Authors(''), - array() - ); - - // set single value by String - - $this->assertEquals( - $this->epub->Authors('John Doe'), - array('John Doe' => 'John Doe') - ); - - // set single value by indexed array - $this->assertEquals( - $this->epub->Authors(array('John Doe')), - array('John Doe' => 'John Doe') - ); - - // remove value with array - $this->assertEquals( - $this->epub->Authors(array()), - array() - ); - - // set single value by associative array - $this->assertEquals( - $this->epub->Authors(array('Doe, John' => 'John Doe')), - array('Doe, John' => 'John Doe') - ); - - // set multi value by string - $this->assertEquals( - $this->epub->Authors('John Doe, Jane Smith'), - array('John Doe' => 'John Doe', 'Jane Smith' => 'Jane Smith') - ); - - // set multi value by indexed array - $this->assertEquals( - $this->epub->Authors(array('John Doe', 'Jane Smith')), - array('John Doe' => 'John Doe', 'Jane Smith' => 'Jane Smith') - ); - - // set multi value by associative array - $this->assertEquals( - $this->epub->Authors(array('Doe, John' => 'John Doe', 'Smith, Jane' => 'Jane Smith')), - array('Doe, John' => 'John Doe', 'Smith, Jane' => 'Jane Smith') - ); - - // check escaping - $this->assertEquals( - $this->epub->Authors(array('Doe, John ' => 'John Doe ')), - array('Doe, John ' => 'John Doe ') - ); - } - - public function testTitle(){ - // get current value - $this->assertEquals( - $this->epub->Title(), - 'Romeo and Juliet' - ); - - // delete current value - $this->assertEquals( - $this->epub->Title(''), - '' - ); - - // get current value - $this->assertEquals( - $this->epub->Title(), - '' - ); - - // set new value - $this->assertEquals( - $this->epub->Title('Foo Bar'), - 'Foo Bar' - ); - - // check escaping - $this->assertEquals( - $this->epub->Title('Foo Bar'), - 'Foo Bar' - ); - } - - public function testSubject(){ - // get current values - $this->assertEquals( - $this->epub->Subjects(), - array('Fiction','Drama','Romance') - ); - - // delete current values with String - $this->assertEquals( - $this->epub->Subjects(''), - array() - ); - - // set new values with String - $this->assertEquals( - $this->epub->Subjects('Fiction, Drama, Romance'), - array('Fiction','Drama','Romance') - ); - - // delete current values with Array - $this->assertEquals( - $this->epub->Subjects(array()), - array() - ); - - // set new values with array - $this->assertEquals( - $this->epub->Subjects(array('Fiction','Drama','Romance')), - array('Fiction','Drama','Romance') - ); - - // check escaping - $this->assertEquals( - $this->epub->Subjects(array('Fiction','Drama ','Romance')), - array('Fiction','Drama ','Romance') - ); - } - - - public function testCover(){ - // read current cover - $cover = $this->epub->Cover(); - $this->assertEquals($cover['mime'],'image/png'); - $this->assertEquals($cover['found'],'OPS/images/cover.png'); - $this->assertEquals(strlen($cover['data']), 657911); - - // delete cover - $cover = $this->epub->Cover(''); - $this->assertEquals($cover['mime'],'image/gif'); - $this->assertEquals($cover['found'],false); - $this->assertEquals(strlen($cover['data']), 42); - - // set new cover (will return a not-found as it's not yet saved) - $cover = $this->epub->Cover('test.jpg','image/jpeg'); - $this->assertEquals($cover['mime'],'image/jpeg'); - $this->assertEquals($cover['found'],'OPS/php-epub-meta-cover.img'); - $this->assertEquals(strlen($cover['data']), 0); - - // save - $this->epub->save(); - - // read now changed cover - $cover = $this->epub->Cover(); - $this->assertEquals($cover['mime'],'image/jpeg'); - $this->assertEquals($cover['found'],'OPS/php-epub-meta-cover.img'); - $this->assertEquals(strlen($cover['data']), filesize('test.jpg')); - } -} diff --git a/util.php b/util.php index 75cb8a8..633fcb9 100644 --- a/util.php +++ b/util.php @@ -1,30 +1,32 @@ <?php -function to_file($input){ - $input = str_replace(' ','_',$input); - $input = str_replace('__','_',$input); - $input = str_replace(',_',',',$input); - $input = str_replace('_,',',',$input); - $input = str_replace('-_','-',$input); - $input = str_replace('_-','-',$input); - $input = str_replace(',','__',$input); +function to_file($input) +{ + $input = str_replace( ' ', '_', $input); + $input = str_replace('__', '_', $input); + $input = str_replace(',_', ',', $input); + $input = str_replace('_,', ',', $input); + $input = str_replace('-_', '-', $input); + $input = str_replace('_-', '-', $input); + $input = str_replace( ',', '__', $input); return $input; } -function book_output($input){ - $input = str_replace('__',',',$input); - $input = str_replace('_',' ',$input); - $input = str_replace(',',', ',$input); - $input = str_replace('-',' - ',$input); - list($author,$title) = explode('-',$input,2); +function book_output($input) +{ + $input = str_replace('__', ',', $input); + $input = str_replace( '_', ' ', $input); + $input = str_replace( ',', ', ', $input); + $input = str_replace( '-', ' - ', $input); + list($author, $title) = explode('-', $input, 2); $author = trim($author); $title = trim($title); - if(!$title){ - $title = $author; + if (!$title) { + $title = $author; $author = ''; } - return '<span class="title">'.htmlspecialchars($title).'</span>'. - '<span class="author">'.htmlspecialchars($author).'</author>'; + return '<span class="title">' . htmlspecialchars($title) . '</span>' . + '<span class="author">' . htmlspecialchars($author) . '</author>'; }