diff options
Diffstat (limited to 'vendor/mikespub/php-epub-meta/src')
18 files changed, 0 insertions, 4823 deletions
diff --git a/vendor/mikespub/php-epub-meta/src/App/Handler.php b/vendor/mikespub/php-epub-meta/src/App/Handler.php deleted file mode 100644 index f1f7547ec..000000000 --- a/vendor/mikespub/php-epub-meta/src/App/Handler.php +++ /dev/null @@ -1,236 +0,0 @@ -<?php -/** - * PHP EPub Meta - App request handler - * - * @author Andreas Gohr <andi@splitbrain.org> - * @author Sébastien Lucas <sebastien@slucas.fr> - * @author Simon Schrape <simon@epubli.com> © 2015 - * @author mikespub - */ - -namespace SebLucas\EPubMeta\App; - -use SebLucas\EPubMeta\EPub; -use SebLucas\EPubMeta\Tools\ZipEdit; -use Exception; - -class Handler -{ - protected string $bookdir; - protected string $rootdir; - protected ?EPub $epub; - protected ?string $error; - - public function __construct(string $bookdir) - { - $this->bookdir = $bookdir; - $this->rootdir = dirname(__DIR__, 2); - } - - /** - * Handle request - * @param mixed $request @todo - * @return void - */ - public function handle($request = null) - { - // proxy google requests - if (isset($_GET['api'])) { - header('application/json; charset=UTF-8'); - echo $this->searchBookApi($_GET['api']); - return; - } - if (!empty($_REQUEST['book'])) { - try { - $book = preg_replace('/[^\w ._-]+/', '', $_REQUEST['book']); - $book = basename($book . '.epub'); // no upper dirs, lowers might be supported later - $this->epub = new EPub($this->bookdir . $book, ZipEdit::class); - } catch (Exception $e) { - $this->error = $e->getMessage(); - } - } - // return image data - if (!empty($_REQUEST['img']) && isset($this->epub)) { - $img = $this->epub->getCoverInfo(); - header('Content-Type: ' . $img['mime']); - echo $img['data']; - return; - } - // save epub data - if (isset($_REQUEST['save']) && isset($this->epub)) { - $this->epub = $this->saveEpubData($this->epub); - if (!$this->error) { - // rename - $new = $this->renameEpubFile($this->epub); - $go = basename($new, '.epub'); - header('Location: ?book=' . rawurlencode($go)); - return; - } - } - $data = []; - $data['bookdir'] = htmlspecialchars($this->bookdir); - $data['booklist'] = ''; - $list = glob($this->bookdir . '/*.epub'); - foreach ($list as $book) { - $base = basename($book, '.epub'); - $name = Util::book_output($base); - $data['booklist'] .= '<li ' . ($base == $_REQUEST['book'] ? 'class="active"' : '') . '>'; - $data['booklist'] .= '<a href="?book=' . htmlspecialchars($base) . '">' . $name . '</a>'; - $data['booklist'] .= '</li>'; - } - if (isset($this->error)) { - $data['alert'] = "alert('" . htmlspecialchars($this->error) . "');"; - } - if (empty($this->epub)) { - $data['license'] = str_replace("\n\n", '</p><p>', htmlspecialchars(file_get_contents($this->rootdir . '/LICENSE'))); - $template = $this->rootdir . '/templates/index.html'; - } else { - $data = $this->getEpubData($this->epub, $data); - $template = $this->rootdir . '/templates/epub.html'; - } - header('Content-Type: text/html; charset=utf-8'); - echo $this->renderTemplate($template, $data); - } - - /** - * Proxy google requests - * @param string $query - * @return string|false - */ - protected function searchBookApi($query) - { - return file_get_contents('https://www.googleapis.com/books/v1/volumes?q=' . rawurlencode($query) . '&maxResults=25&printType=books&projection=full'); - } - - /** - * Get Epub data - * @param EPub $epub - * @param array<string, string> $data - * @return array<string, string> - */ - protected function getEpubData($epub, $data = []) - { - $data['book'] = htmlspecialchars($_REQUEST['book']); - $data['title'] = htmlspecialchars($epub->getTitle()); - $data['authors'] = ''; - $count = 0; - foreach ($epub->getAuthors() as $as => $name) { - $data['authors'] .= '<p>'; - $data['authors'] .= '<input type="text" name="authorname[' . $count . ']" value="' . htmlspecialchars($name) . '" />'; - $data['authors'] .= ' (<input type="text" name="authoras[' . $count . ']" value="' . htmlspecialchars($as) . '" />)'; - $data['authors'] .= '</p>'; - $count++; - } - $data['cover'] = '?book=' . htmlspecialchars($_REQUEST['book']) . '&img=1'; - $c = $epub->getCoverInfo(); - $data['imgclass'] = $c['found'] ? 'hasimg' : 'noimg'; - $data['description'] = htmlspecialchars($epub->getDescription()); - $data['subjects'] = htmlspecialchars(join(', ', $epub->getSubjects())); - $data['publisher'] = htmlspecialchars($epub->getPublisher()); - $data['copyright'] = htmlspecialchars($epub->getCopyright()); - $data['language'] = htmlspecialchars($epub->getLanguage()); - $data['isbn'] = htmlspecialchars($epub->getISBN()); - return $data; - } - - /** - * Save Epub data - * @param EPub $epub - * @return EPub - */ - protected function saveEpubData($epub) - { - $epub->setTitle($_POST['title']); - $epub->setDescription($_POST['description']); - $epub->setLanguage($_POST['language']); - $epub->setPublisher($_POST['publisher']); - $epub->setCopyright($_POST['copyright']); - $epub->setIsbn($_POST['isbn']); - $epub->setSubjects($_POST['subjects']); - - $authors = []; - foreach ((array) $_POST['authorname'] as $num => $name) { - if ($name) { - $as = $_POST['authoras'][$num]; - if (!$as) { - $as = $name; - } - $authors[$as] = $name; - } - } - $epub->setAuthors($authors); - - // handle image - $cover = ''; - if (preg_match('/^https?:\/\//i', $_POST['coverurl'])) { - $data = @file_get_contents($_POST['coverurl']); - if ($data) { - $cover = tempnam(sys_get_temp_dir(), 'epubcover'); - file_put_contents($cover, $data); - unset($data); - } - } elseif(is_uploaded_file($_FILES['coverfile']['tmp_name'])) { - $cover = $_FILES['coverfile']['tmp_name']; - } - if ($cover) { - $info = @getimagesize($cover); - if (preg_match('/^image\/(gif|jpe?g|png)$/', $info['mime'])) { - $epub->setCoverInfo($cover, $info['mime']); - } else { - $this->error = 'Not a valid image file' . $cover; - } - } - - // save the ebook - try { - $epub->save(); - } catch (Exception $e) { - $this->error = $e->getMessage(); - } - - // clean up temporary cover file - if ($cover) { - @unlink($cover); - } - - return $epub; - } - - /** - * Rename Epub file - * @param EPub $epub - * @return string - */ - protected function renameEpubFile($epub) - { - $author = array_keys($epub->getAuthors())[0]; - $title = $epub->getTitle(); - $new = Util::to_file($author . '-' . $title); - $new = $this->bookdir . $new . '.epub'; - $old = $epub->file(); - if (realpath($new) != realpath($old)) { - if (!@rename($old, $new)) { - $new = $old; //rename failed, stay here - } - } - return $new; - } - - /** - * Render template with data - * @param string $template - * @param array<string, string> $data - * @return string - */ - protected function renderTemplate($template, $data) - { - if (!file_exists($template)) { - throw new Exception('Invalid template ' . htmlspecialchars($template)); - } - $content = file_get_contents($template); - foreach ($data as $name => $value) { - $content = preg_replace('/{{\s*' . $name . '\s*}}/', $value, $content); - } - return $content; - } -} diff --git a/vendor/mikespub/php-epub-meta/src/App/Util.php b/vendor/mikespub/php-epub-meta/src/App/Util.php deleted file mode 100644 index 730e9ec95..000000000 --- a/vendor/mikespub/php-epub-meta/src/App/Util.php +++ /dev/null @@ -1,55 +0,0 @@ -<?php -/** - * PHP EPub Meta utility functions for App interface - * - * @author Andreas Gohr <andi@splitbrain.org> - * @author Sébastien Lucas <sebastien@slucas.fr> - * @author Simon Schrape <simon@epubli.com> © 2015 - * @author mikespub - */ - -namespace SebLucas\EPubMeta\App; - -class Util -{ - /** - * Summary of to_file - * @param string $input - * @return string - */ - public static 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; - } - - /** - * Summary of book_output - * @param string $input - * @return string - */ - public static function book_output($input) - { - $input = str_replace('__', ',', $input); - $input = str_replace('_', ' ', $input); - $input = str_replace(',', ', ', $input); - $input = str_replace('-', ' - ', $input); - [$author, $title] = explode('-', $input, 2); - $author = trim($author); - $title = trim($title); - - if (!$title) { - $title = $author; - $author = ''; - } - - return '<span class="title">' . htmlspecialchars($title) . '</span>' . - '<span class="author">' . htmlspecialchars($author) . '</author>'; - } -} diff --git a/vendor/mikespub/php-epub-meta/src/Contents/Nav.php b/vendor/mikespub/php-epub-meta/src/Contents/Nav.php deleted file mode 100644 index e8eb980a6..000000000 --- a/vendor/mikespub/php-epub-meta/src/Contents/Nav.php +++ /dev/null @@ -1,63 +0,0 @@ -<?php - -namespace SebLucas\EPubMeta\Contents; - -/** - * EPUB NAV structure for EPUB 3 - * - * @author Simon Schrape <simon@epubli.com> - */ -class Nav -{ - /** @var string from main document */ - protected $docTitle; - /** @var string from main document */ - protected $docAuthor; - /** @var NavPointList */ - protected $navMap; - - /** - * Summary of __construct - * @param string $title - * @param string $author - */ - public function __construct($title, $author) - { - $this->docTitle = $title; - $this->docAuthor = $author; - $this->navMap = new NavPointList(); - } - - /** - * @return string - */ - public function getDocTitle() - { - return $this->docTitle; - } - - /** - * @return string - */ - public function getDocAuthor() - { - return $this->docAuthor; - } - - /** - * @return NavPointList - */ - public function getNavMap() - { - return $this->navMap; - } - - /** - * @param string $file - * @return array|NavPoint[] - */ - public function findNavPointsForFile($file) - { - return $this->getNavMap()->findNavPointsForFile($file); - } -} diff --git a/vendor/mikespub/php-epub-meta/src/Contents/NavPoint.php b/vendor/mikespub/php-epub-meta/src/Contents/NavPoint.php deleted file mode 100644 index 7ffb29dca..000000000 --- a/vendor/mikespub/php-epub-meta/src/Contents/NavPoint.php +++ /dev/null @@ -1,109 +0,0 @@ -<?php - -namespace SebLucas\EPubMeta\Contents; - -/** - * An EPUB TOC navigation point. - * - * @author Simon Schrape <simon@epubli.com> - */ -class NavPoint -{ - /** @var string */ - protected $id; - /** @var string */ - protected $class; - /** @var int */ - protected $playOrder; - /** @var string */ - protected $navLabel; - /** @var string */ - protected $contentSourceFile; - /** @var string */ - protected $contentSourceFragment; - /** @var NavPointList */ - protected $children; - - /** - * @param string $id - * @param string $class - * @param int $playOrder - * @param string $label - * @param string $contentSource - */ - public function __construct($id, $class, $playOrder, $label, $contentSource) - { - $this->id = $id; - $this->class = $class; - $this->playOrder = $playOrder; - $this->navLabel = $label; - $contentSourceParts = explode('#', $contentSource, 2); - $this->contentSourceFile = $contentSourceParts[0]; - $this->contentSourceFragment = $contentSourceParts[1] ?? null; - $this->children = new NavPointList(); - } - - /** - * @return string - */ - public function getId() - { - return $this->id; - } - - /** - * @return string - */ - public function getClass() - { - return $this->class; - } - - /** - * @return int - */ - public function getPlayOrder() - { - return $this->playOrder; - } - - /** - * @return string - */ - public function getNavLabel() - { - return $this->navLabel; - } - - /** - * @return string - */ - public function getContentSource() - { - return $this->contentSourceFile . ($this->contentSourceFragment ? '#' . $this->contentSourceFragment : ''); - } - - /** - * @return string - */ - public function getContentSourceFile() - { - return $this->contentSourceFile; - } - - /** - * @return string - */ - public function getContentSourceFragment() - { - return $this->contentSourceFragment; - } - - /** - * @return NavPointList - */ - public function getChildren() - { - return $this->children; - } -} diff --git a/vendor/mikespub/php-epub-meta/src/Contents/NavPointList.php b/vendor/mikespub/php-epub-meta/src/Contents/NavPointList.php deleted file mode 100644 index 540d2f876..000000000 --- a/vendor/mikespub/php-epub-meta/src/Contents/NavPointList.php +++ /dev/null @@ -1,65 +0,0 @@ -<?php - -namespace SebLucas\EPubMeta\Contents; - -use ArrayIterator; - -/** - * A list of EPUB TOC navigation points. - * - * @author Simon Schrape <simon@epubli.com> - * @author mikespub - * @extends ArrayIterator<int, NavPoint> - */ -class NavPointList extends ArrayIterator -{ - public function __construct() {} - - /** - * @return NavPoint - */ - public function first() - { - $this->rewind(); - return $this->current(); - } - - /** - * @return NavPoint - */ - public function last() - { - $this->seek($this->count() - 1); - return $this->current(); - } - - /** - * @param NavPoint $navPoint - * @return void - * @deprecated 2.1.0 use normal append() instead - */ - public function addNavPoint(NavPoint $navPoint) - { - $this->append($navPoint); - } - - /** - * @param string $file - * - * @return array|NavPoint[] - */ - public function findNavPointsForFile($file) - { - $matches = []; - foreach ($this as $navPoint) { - if ($navPoint->getContentSourceFile() == $file) { - $matches[] = $navPoint; - } - $childMatches = $navPoint->getChildren()->findNavPointsForFile($file); - if (count($childMatches)) { - $matches = array_merge($matches, $childMatches); - } - } - return $matches; - } -} diff --git a/vendor/mikespub/php-epub-meta/src/Contents/Spine.php b/vendor/mikespub/php-epub-meta/src/Contents/Spine.php deleted file mode 100644 index e6dfc2a84..000000000 --- a/vendor/mikespub/php-epub-meta/src/Contents/Spine.php +++ /dev/null @@ -1,198 +0,0 @@ -<?php - -namespace SebLucas\EPubMeta\Contents; - -use SebLucas\EPubMeta\Data\Item; -use ArrayAccess; -use Countable; -use Iterator; -use BadMethodCallException; - -/** - * EPUB spine structure - * - * @author Simon Schrape <simon@epubli.com> - * @implements \Iterator<int, Item> - * @implements \ArrayAccess<int, Item> - */ -class Spine implements Iterator, Countable, ArrayAccess -{ - /** @var Item */ - protected $tocItem; - protected string $tocFormat; - /** @var array|Item[] The ordered list of all Items in this Spine. */ - protected $items = []; - - /** - * Spine Constructor. - * - * @param Item $tocItem The TOC Item of this Spine. - * @param string $tocFormat The TOC Format of this Spine (Toc or Nav). - */ - public function __construct(Item $tocItem, string $tocFormat) - { - $this->tocItem = $tocItem; - $this->tocFormat = $tocFormat; - } - - /** - * Get the TOC Item of this Spine. - * - * @return Item - */ - public function getTocItem() - { - return $this->tocItem; - } - - /** - * Get the TOC Format of this Spine. - * - * @return string - */ - public function getTocFormat() - { - return $this->tocFormat; - } - - /** - * Append an Item to this Spine. - * - * @param Item $item The Item to append to this Spine. - * @return void - */ - public function appendItem(Item $item) - { - $this->items[] = $item; - } - - /** - * Return the current Item while iterating this Spine. - * - * @link http://php.net/manual/en/iterator.current.php - * @return Item - */ - public function current(): Item - { - return current($this->items); - } - - /** - * Move forward to next Item while iterating this Spine. - * @link http://php.net/manual/en/iterator.next.php - * @return void Any returned value is ignored. - */ - public function next(): void - { - next($this->items); - } - - /** - * Return the index of the current Item while iterating this Spine. - * - * @link http://php.net/manual/en/iterator.key.php - * @return int|null on success, or null on failure. - */ - public function key(): ?int - { - return key($this->items); - } - - /** - * Checks if current Iterator position is valid. - * - * @link http://php.net/manual/en/iterator.valid.php - * @return boolean true on success or false on failure. - */ - public function valid(): bool - { - return (bool) current($this->items); - } - - /** - * Rewind the Iterator to the first element. - * - * @link http://php.net/manual/en/iterator.rewind.php - * @return void Any returned value is ignored. - */ - public function rewind(): void - { - reset($this->items); - } - - /** - * Get the first Item of this Spine. - * - * @return Item - */ - public function first() - { - return reset($this->items); - } - - /** - * Get the last Item of this Spine. - * - * @return Item - */ - public function last() - { - return end($this->items); - } - - /** - * Count items of this Spine. - * - * @link https://php.net/manual/en/countable.count.php - * @return int The number of Items contained in this Spine. - */ - public function count(): int - { - return count($this->items); - } - - /** - * Whether a offset exists - * @link https://php.net/manual/en/arrayaccess.offsetexists.php - * @param int $offset An offset to check for. - * @return boolean true on success or false on failure. - */ - public function offsetExists($offset): bool - { - return isset($this->items[$offset]); - } - - /** - * Offset to retrieve - * @link https://php.net/manual/en/arrayaccess.offsetget.php - * @param int $offset The offset to retrieve. - * @return Item - */ - public function offsetGet($offset): Item - { - return $this->items[$offset]; - } - - /** - * Offset to set - * @link https://php.net/manual/en/arrayaccess.offsetset.php - * @param mixed $offset The offset to assign the value to. - * @param mixed $value The value to set. - * @throws BadMethodCallException - */ - public function offsetSet($offset, $value): void - { - throw new BadMethodCallException("Only reading array access is supported!"); - } - - /** - * Offset to unset - * @link https://php.net/manual/en/arrayaccess.offsetunset.php - * @param mixed $offset The offset to unset. - * @throws BadMethodCallException - */ - public function offsetUnset($offset): void - { - throw new BadMethodCallException("Only reading array access is supported!"); - } -} diff --git a/vendor/mikespub/php-epub-meta/src/Contents/Toc.php b/vendor/mikespub/php-epub-meta/src/Contents/Toc.php deleted file mode 100644 index 7992c4a8e..000000000 --- a/vendor/mikespub/php-epub-meta/src/Contents/Toc.php +++ /dev/null @@ -1,63 +0,0 @@ -<?php - -namespace SebLucas\EPubMeta\Contents; - -/** - * EPUB TOC structure for EPUB 2 - * - * @author Simon Schrape <simon@epubli.com> - */ -class Toc -{ - /** @var string */ - protected $docTitle; - /** @var string */ - protected $docAuthor; - /** @var NavPointList */ - protected $navMap; - - /** - * Summary of __construct - * @param string $title - * @param string $author - */ - public function __construct($title, $author) - { - $this->docTitle = $title; - $this->docAuthor = $author; - $this->navMap = new NavPointList(); - } - - /** - * @return string - */ - public function getDocTitle() - { - return $this->docTitle; - } - - /** - * @return string - */ - public function getDocAuthor() - { - return $this->docAuthor; - } - - /** - * @return NavPointList - */ - public function getNavMap() - { - return $this->navMap; - } - - /** - * @param string $file - * @return array|NavPoint[] - */ - public function findNavPointsForFile($file) - { - return $this->getNavMap()->findNavPointsForFile($file); - } -} diff --git a/vendor/mikespub/php-epub-meta/src/Data/Item.php b/vendor/mikespub/php-epub-meta/src/Data/Item.php deleted file mode 100644 index 271462920..000000000 --- a/vendor/mikespub/php-epub-meta/src/Data/Item.php +++ /dev/null @@ -1,193 +0,0 @@ -<?php - -namespace SebLucas\EPubMeta\Data; - -use SebLucas\EPubMeta\Tools\HtmlTools; -use DOMDocument; -use DOMElement; -use DOMText; -use DOMXPath; -use Exception; - -/** - * An item of the EPUB manifest. - * - * @author Simon Schrape <simon@epubli.com> - */ -class Item -{ - public const XHTML = 'application/xhtml+xml'; - /** @var string */ - protected $id; - /** @var string The path to the corresponding file. */ - protected $href; - /** @var string */ - protected $mediaType; - /** @var callable|null A callable to get data from the referenced file. */ - protected $dataCallable; - /** @var string The data read from the referenced file. */ - protected $data; - /** @var int The size of the referenced file. */ - protected $size; - - /** - * @param string $id This Item’s identifier. - * @param string $href The path to the corresponding file. - * @param callable $dataCallable A callable to get data from the referenced file. - * @param int $size The size of the referenced file. - * @param string|null $mediaType The media type of the corresponding file. If omitted XHTML is assumed. - */ - public function __construct($id, $href, $dataCallable, $size, $mediaType = null) - { - $this->id = $id; - $this->href = $href; - $this->dataCallable = $dataCallable; - $this->size = $size; - $this->mediaType = $mediaType ?: static::XHTML; - } - - /** - * @return string - */ - public function getId() - { - return $this->id; - } - - /** - * @return string - */ - public function getHref() - { - return $this->href; - } - - /** - * @return string - */ - public function getMediaType() - { - return $this->mediaType; - } - - /** - * Extract (a part of) the contents from the referenced XML file. - * - * @param string|null $fragmentBegin ID of the element where to start reading the contents. - * @param string|null $fragmentEnd ID of the element where to stop reading the contents. - * @param bool $keepMarkup Whether to keep the XHTML markup rather than extracted plain text. - * @return string The contents of that fragment. - * @throws Exception - */ - public function getContents($fragmentBegin = null, $fragmentEnd = null, $keepMarkup = false) - { - $dom = new DOMDocument(); - $dom->loadXML(HtmlTools::convertEntitiesNamedToNumeric($this->getData())); - - // get the starting point - if ($fragmentBegin) { - $xp = new DOMXPath($dom); - $node = $xp->query("//*[@id='$fragmentBegin']")->item(0); - if (!$node) { - throw new Exception("Begin of fragment not found: No element with ID $fragmentBegin!"); - } - } else { - $node = $dom->getElementsByTagName('body')->item(0) ?: $dom->documentElement; - } - - $allowableTags = [ - 'br', - 'p', - 'h1', - 'h2', - 'h3', - 'h4', - 'h5', - 'span', - 'div', - 'i', - 'strong', - 'b', - 'table', - 'td', - 'th', - 'tr', - ]; - $contents = ''; - $endTags = []; - /** @var DOMElement|DOMText $node */ - // traverse DOM structure till end point is reached, accumulating the contents - while ($node && (!$fragmentEnd || !$node->hasAttributes() || $node->getAttribute('id') != $fragmentEnd)) { - if ($node instanceof DOMText) { - // when encountering a text node append its value to the contents - $contents .= $keepMarkup ? htmlspecialchars($node->nodeValue) : $node->nodeValue; - } elseif ($node instanceof DOMElement) { - $tag = $node->localName; - if ($keepMarkup && in_array($tag, $allowableTags)) { - $contents .= "<$tag>"; - $endTags[] = "</$tag>"; - } elseif (HtmlTools::isBlockLevelElement($tag)) { - // add whitespace between contents of adjacent blocks - $endTags[] = PHP_EOL; - } else { - $endTags[] = ''; - } - - if ($node->hasChildNodes()) { - // step into - $node = $node->firstChild; - continue; - } - } - - // leave node - while ($node) { - if ($node instanceof DOMElement) { - $contents .= array_pop($endTags); - } - - if ($node->nextSibling) { - // step right - $node = $node->nextSibling; - break; - } elseif ($node = $node->parentNode) { - // step out - continue; - } elseif ($fragmentEnd) { - // reached end of DOM without finding fragment end - throw new Exception("End of fragment not found: No element with ID $fragmentEnd!"); - } - } - } - while ($endTags) { - $contents .= array_pop($endTags); - } - - return $contents; - } - - /** - * Get the file data. - * - * @return string The binary data of the corresponding file. - */ - public function getData() - { - if ($this->dataCallable) { - $this->data = call_user_func($this->dataCallable); - $this->dataCallable = null; - } - - return $this->data; - } - - /** - * Get the size of the corresponding file. - * - * @return int - */ - public function getSize() - { - return $this->size ?: strlen($this->getData()); - } -} diff --git a/vendor/mikespub/php-epub-meta/src/Data/Manifest.php b/vendor/mikespub/php-epub-meta/src/Data/Manifest.php deleted file mode 100644 index 428b2cc02..000000000 --- a/vendor/mikespub/php-epub-meta/src/Data/Manifest.php +++ /dev/null @@ -1,174 +0,0 @@ -<?php - -namespace SebLucas\EPubMeta\Data; - -use ArrayAccess; -use Countable; -use Exception; -use BadMethodCallException; -use Iterator; - -/** - * EPUB manifest structure - * - * @author Simon Schrape <simon@epubli.com> - * @implements \Iterator<string, Item> - * @implements \ArrayAccess<string, Item> - */ -class Manifest implements Iterator, Countable, ArrayAccess -{ - /** @var array|Item[] The map of all Items in this Manifest indexed by their IDs. */ - protected $items = []; - - /** - * Create and add an Item with the given properties. - * - * @param string $id The identifier of the new item. - * @param string $href The relative path of the referenced file in the EPUB. - * @param callable $callable A callable to get data from the referenced file in the EPUB. - * @param int $size The size of the referenced file in the EPUB. - * @param string|null $mediaType - * @return Item The newly created Item. - * @throws Exception If $id is already taken. - */ - public function createItem($id, $href, $callable, $size, $mediaType = null) - { - if (isset($this->items[$id])) { - throw new Exception("Item with ID $id already exists!"); - } - $item = new Item($id, $href, $callable, $size, $mediaType); - $this->items[$id] = $item; - - return $item; - } - - /** - * Return the current Item while iterating this Manifest. - * - * @link http://php.net/manual/en/iterator.current.php - * @return Item - */ - public function current(): Item - { - return current($this->items); - } - - /** - * Move forward to next Item while iterating this Manifest. - * @link http://php.net/manual/en/iterator.next.php - * @return void Any returned value is ignored. - */ - public function next(): void - { - next($this->items); - } - - /** - * Return the ID of the current Item while iterating this Manifest. - * - * @link http://php.net/manual/en/iterator.key.php - * @return string|null on success, or null on failure. - */ - public function key(): ?string - { - return key($this->items); - } - - /** - * Checks if current Iterator position is valid. - * - * @link http://php.net/manual/en/iterator.valid.php - * @return boolean true on success or false on failure. - */ - public function valid(): bool - { - return (bool) current($this->items); - } - - /** - * Rewind the Iterator to the first element. - * - * @link http://php.net/manual/en/iterator.rewind.php - * @return void Any returned value is ignored. - */ - public function rewind(): void - { - reset($this->items); - } - - /** - * Get the first Item of this Manifest. - * - * @return Item - */ - public function first() - { - return reset($this->items); - } - - /** - * Get the last Item of this Manifest. - * - * @return Item - */ - public function last() - { - return end($this->items); - } - - /** - * Count items of this Manifest. - * - * @link https://php.net/manual/en/countable.count.php - * @return int The number of Items contained in this Manifest. - */ - public function count(): int - { - return count($this->items); - } - - /** - * Whether a offset exists - * @link https://php.net/manual/en/arrayaccess.offsetexists.php - * @param string $offset An offset to check for. - * @return boolean true on success or false on failure. - */ - public function offsetExists($offset): bool - { - return isset($this->items[$offset]); - } - - /** - * Offset to retrieve - * @link https://php.net/manual/en/arrayaccess.offsetget.php - * @param string $offset The offset to retrieve. - * @return Item - */ - public function offsetGet($offset): Item - { - return $this->items[$offset]; - } - - /** - * Offset to set - * @link https://php.net/manual/en/arrayaccess.offsetset.php - * @param mixed $offset The offset to assign the value to. - * @param mixed $value The value to set. - * @throws BadMethodCallException - */ - public function offsetSet($offset, $value): void - { - throw new BadMethodCallException("Only reading array access is supported!"); - } - - /** - * Offset to unset - * @link https://php.net/manual/en/arrayaccess.offsetunset.php - * @param mixed $offset The offset to unset. - * @throws BadMethodCallException - */ - public function offsetUnset($offset): void - { - throw new BadMethodCallException("Only reading array access is supported!"); - } -} diff --git a/vendor/mikespub/php-epub-meta/src/Dom/Element.php b/vendor/mikespub/php-epub-meta/src/Dom/Element.php deleted file mode 100644 index 2a7058eaa..000000000 --- a/vendor/mikespub/php-epub-meta/src/Dom/Element.php +++ /dev/null @@ -1,196 +0,0 @@ -<?php -/** - * PHP EPub Meta library - * - * @author Andreas Gohr <andi@splitbrain.org> - * @author Sébastien Lucas <sebastien@slucas.fr> - */ - -namespace SebLucas\EPubMeta\Dom; - -use DOMElement; - -/** - * EPUB-specific subclass of DOMElement - * - * Source: https://github.com/splitbrain/php-epub-meta - * @author Andreas Gohr <andi@splitbrain.org> © 2012 - * @author Simon Schrape <simon@epubli.com> © 2015–2018 - * @author Sébastien Lucas <sebastien@slucas.fr> - * - * @property string $nodeValueUnescaped - */ -class Element extends DOMElement -{ - /** @var array<string, string> */ - public static $namespaces = [ - '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/', - ]; - - /** - * Summary of __construct - * @param string $name - * @param string $value - * @param string $namespaceUri - */ - public function __construct($name, $value = '', $namespaceUri = '') - { - [$prefix, $name] = $this->splitQualifiedName($name); - $value = htmlspecialchars($value); - if (!$namespaceUri && $prefix) { - //$namespaceUri = XmlNamespace::getUri($prefix); - $namespaceUri = static::$namespaces[$prefix]; - } - parent::__construct($name, $value, $namespaceUri); - } - - /** - * Summary of __get - * @param string $name - * @return string|null - */ - public function __get($name) - { - switch ($name) { - case 'nodeValueUnescaped': - return htmlspecialchars_decode($this->nodeValue); - } - - return null; - } - - /** - * Summary of __set - * @param string $name - * @param mixed $value - * @return void - */ - public function __set($name, $value) - { - switch ($name) { - case 'nodeValueUnescaped': - $this->nodeValue = htmlspecialchars($value); - } - } - - /** - * Create and append a new child - * - * Works with our epub namespaces and omits default namespaces - * @param string $name - * @param string $value - * @return Element|bool - */ - public function newChild($name, $value = '') - { - [$localName, $namespaceUri] = $this->getNameContext($name); - - // this doesn't call the constructor: $node = $this->ownerDocument->createElement($name,$value); - $node = new Element($namespaceUri ? $name : $localName, $value, $namespaceUri); - - /** @var Element $node */ - $node = $this->appendChild($node); - return $node; - } - - /** - * Simple EPUB namespace aware attribute getter - * @param string $name - * @return string - */ - public function getAttrib($name) - { - [$localName, $namespaceUri] = $this->getNameContext($name); - - // return value if none was given - if ($namespaceUri) { - return $this->getAttributeNS($namespaceUri, $localName); - } else { - return $this->getAttribute($localName); - } - } - - /** - * Simple EPUB namespace aware attribute setter - * @param string $name - * @param mixed $value - * @return void - */ - public function setAttrib($name, $value) - { - [$localName, $namespaceUri] = $this->getNameContext($name); - - if ($namespaceUri) { - $this->setAttributeNS($namespaceUri, $localName, $value); - } else { - $this->setAttribute($localName, $value); - } - } - - /** - * Simple EPUB namespace aware attribute remover - * @param string $name - * @return void - */ - public function removeAttrib($name) - { - [$localName, $namespaceUri] = $this->getNameContext($name); - - if ($namespaceUri) { - $this->removeAttributeNS($namespaceUri, $localName); - } else { - $this->removeAttribute($localName); - } - } - - /** - * Remove this node from the DOM - * @return void - */ - public function delete() - { - $this->parentNode->removeChild($this); - } - - /** - * Split given name in namespace prefix and local part - * - * @param string $name - * @return array<string> (prefix, name) - */ - protected function splitQualifiedName($name) - { - $list = explode(':', $name, 2); - if (count($list) < 2) { - array_unshift($list, ''); - } - - return $list; - } - - /** - * @param string $name - * @return array<string> - */ - protected function getNameContext($name) - { - [$prefix, $localName] = $this->splitQualifiedName($name); - - $namespaceUri = ''; - if ($prefix) { - //$namespaceUri = XmlNamespace::getUri($prefix); - $namespaceUri = static::$namespaces[$prefix]; - if ( - !$this->namespaceURI && $this->isDefaultNamespace($namespaceUri) - || $this->namespaceURI == $namespaceUri - ) { - $namespaceUri = ''; - } - } - - return [$localName, $namespaceUri]; - } -} diff --git a/vendor/mikespub/php-epub-meta/src/Dom/XPath.php b/vendor/mikespub/php-epub-meta/src/Dom/XPath.php deleted file mode 100644 index 4d208c1cd..000000000 --- a/vendor/mikespub/php-epub-meta/src/Dom/XPath.php +++ /dev/null @@ -1,32 +0,0 @@ -<?php -/** - * PHP EPub Meta library - * - * @author Andreas Gohr <andi@splitbrain.org> - * @author Sébastien Lucas <sebastien@slucas.fr> - */ - -namespace SebLucas\EPubMeta\Dom; - -use DOMDocument; -use DOMXPath; - -/** - * EPUB-specific subclass of DOMXPath - * - * Source: https://github.com/splitbrain/php-epub-meta - * @author Andreas Gohr <andi@splitbrain.org> © 2012 - * @author Simon Schrape <simon@epubli.com> © 2015–2018 - * @author Sébastien Lucas <sebastien@slucas.fr> - */ -class XPath extends DOMXPath -{ - public function __construct(DOMDocument $doc) - { - parent::__construct($doc); - - foreach (Element::$namespaces as $prefix => $namespaceUri) { - $this->registerNamespace($prefix, $namespaceUri); - } - } -} diff --git a/vendor/mikespub/php-epub-meta/src/EPub.php b/vendor/mikespub/php-epub-meta/src/EPub.php deleted file mode 100644 index 171fd7641..000000000 --- a/vendor/mikespub/php-epub-meta/src/EPub.php +++ /dev/null @@ -1,2126 +0,0 @@ -<?php -/** - * PHP EPub Meta library - * - * @author Andreas Gohr <andi@splitbrain.org> - * @author Sébastien Lucas <sebastien@slucas.fr> - * @author Simon Schrape <simon@epubli.com> © 2015 - */ - -namespace SebLucas\EPubMeta; - -use SebLucas\EPubMeta\Dom\Element as EpubDomElement; -use SebLucas\EPubMeta\Dom\XPath as EpubDomXPath; -use SebLucas\EPubMeta\Data\Manifest; -use SebLucas\EPubMeta\Contents\Nav; -use SebLucas\EPubMeta\Contents\Spine; -use SebLucas\EPubMeta\Contents\NavPoint as TocNavPoint; -use SebLucas\EPubMeta\Contents\NavPointList as TocNavPointList; -use SebLucas\EPubMeta\Contents\Toc; -use SebLucas\EPubMeta\Tools\ZipEdit; -use SebLucas\EPubMeta\Tools\ZipFile; -use DOMDocument; -use DOMElement; -use DOMNodeList; -use Exception; -use InvalidArgumentException; -use JsonException; -use ZipArchive; - -class EPub -{ - /** Identifier for cover image inserted by this lib. */ - public const COVER_ID = 'php-epub-meta-cover'; - /** Identifier for title page inserted by this lib. */ - public const TITLE_PAGE_ID = 'php-epub-meta-titlepage'; - public const METADATA_FILE = 'META-INF/container.xml'; - public const MIME_TYPE = 'application/epub+zip'; - public const BOOKMARK_FILE = 'META-INF/calibre_bookmarks.txt'; - public const EPUB_FILE_TYPE_MAGIC = "encoding=json+base64:\n"; - /** @var array<int, array<string>> */ - public static $encodeNameReplace = [ - ['/', '-'], - ['~SLASH~', '~DASH~'], - ]; - /** @var array<int, array<string>> */ - public static $decodeNameReplace = [ - ['~SLASH~', '~DASH~'], - ['/', '-'], - ]; - - /** @var DOMDocument */ - public $xml; //FIXME: change to protected, later - /** @var DOMDocument|null */ - public $toc; - /** @var DOMDocument|null */ - public $nav; - /** @var EpubDomXPath */ - protected $xpath; - /** @var EpubDomXPath */ - protected $toc_xpath; - /** @var EpubDomXPath */ - protected $nav_xpath; - protected string $file; - protected string $meta; - /** @var ZipEdit|ZipFile */ - protected $zip; - protected string $zipClass; - protected string $coverpath = ''; - /** @var mixed */ - protected $namespaces; - protected string $imagetoadd = ''; - /** @var array<mixed> A map of ZIP items mapping filenames to file sizes */ - protected $zipSizeMap; - /** @var Manifest|null The manifest (catalog of files) of this EPUB */ - protected $manifest; - /** @var Spine|null The spine structure of this EPUB */ - protected $spine; - /** @var Toc|Nav|null The TOC structure of this EPUB */ - protected $tocnav; - protected int $epubVersion = 0; - - /** - * Constructor - * - * @param string $file path to epub file to work on - * @param string $zipClass class to handle zip - ZipFile is read-only - * @throws Exception if metadata could not be loaded - */ - public function __construct($file, $zipClass = ZipFile::class) - { - if (!is_file($file)) { - throw new Exception("Epub file does not exist!"); - } - if (filesize($file) <= 0) { - throw new Exception("Epub file is empty!"); - } - // open file - $this->file = $file; - $this->openZipFile($zipClass); - - // read container data - $this->loadMetadata(); - } - - /** - * Summary of openZipFile - * @param string $zipClass - * @throws \Exception - * @return void - */ - public function openZipFile($zipClass) - { - $this->zip = new $zipClass(); - if (!$this->zip->Open($this->file)) { - throw new Exception('Failed to read epub file'); - } - $this->zipClass = $zipClass; - } - - /** - * Summary of loadMetadata - * @throws \Exception - * @return void - */ - public function loadMetadata() - { - if (!$this->zip->FileExists(static::METADATA_FILE)) { - throw new Exception('Unable to find ' . static::METADATA_FILE); - } - - $data = $this->zip->FileRead(static::METADATA_FILE); - if ($data == false) { - throw new Exception('Failed to access epub container data'); - } - $xml = new DOMDocument(); - $xml->registerNodeClass(DOMElement::class, EpubDomElement::class); - $xml->loadXML($data); - $xpath = new EpubDomXPath($xml); - $nodes = $xpath->query('//n:rootfiles/n:rootfile[@media-type="application/oebps-package+xml"]'); - $this->meta = static::getAttr($nodes, '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->loadXmlData($data); - - $this->zipSizeMap = $this->loadSizeMap($this->file); - } - - /** - * Summary of loadXmlData - * @param string $data - * @return void - */ - public function loadXmlData($data) - { - $this->xml = new DOMDocument(); - $this->xml->registerNodeClass(DOMElement::class, EpubDomElement::class); - $this->xml->loadXML($data); - $this->xml->formatOutput = true; - $this->xpath = new EpubDomXPath($this->xml); - } - - /** - * Summary of initSpineComponent - * @throws \Exception - * @return void - */ - public function initSpineComponent() - { - $nodes = $this->xpath->query('//opf:spine'); - $tocid = static::getAttr($nodes, 'toc'); - if (empty($tocid)) { - $nodes = $this->xpath->query('//opf:manifest/opf:item[@properties="nav"]'); - $navhref = static::getAttr($nodes, 'href'); - $navpath = $this->getFullPath($navhref); - // read epub nav doc - if (!$this->zip->FileExists($navpath)) { - throw new Exception('Unable to find ' . $navpath); - } - $data = $this->zip->FileRead($navpath); - $this->loadNavData($data); - return; - } - $nodes = $this->xpath->query('//opf:manifest/opf:item[@id="' . $tocid . '"]'); - $tochref = static::getAttr($nodes, 'href'); - $tocpath = $this->getFullPath($tochref); - // read epub toc - if (!$this->zip->FileExists($tocpath)) { - throw new Exception('Unable to find ' . $tocpath); - } - - $data = $this->zip->FileRead($tocpath); - $this->loadTocData($data); - } - - /** - * Summary of loadNavData - * @param string $data - * @return void - */ - public function loadNavData($data) - { - $this->nav = new DOMDocument(); - $this->nav->registerNodeClass(DOMElement::class, EpubDomElement::class); - $this->nav->loadXML($data); - $this->nav_xpath = new EpubDomXPath($this->nav); - $rootNamespace = $this->nav->lookupNamespaceUri($this->nav->namespaceURI); - $this->nav_xpath->registerNamespace('x', $rootNamespace); - } - - /** - * Summary of loadTocData - * @param string $data - * @return void - */ - public function loadTocData($data) - { - $this->toc = new DOMDocument(); - $this->toc->registerNodeClass(DOMElement::class, EpubDomElement::class); - $this->toc->loadXML($data); - $this->toc_xpath = new EpubDomXPath($this->toc); - $rootNamespace = $this->toc->lookupNamespaceUri($this->toc->namespaceURI); - $this->toc_xpath->registerNamespace('x', $rootNamespace); - } - - /** - * Get the ePub version - * - * @return int The number of the ePub version (2 or 3 for now) or 0 if not found - */ - public function getEpubVersion() - { - if ($this->epubVersion) { - return $this->epubVersion; - } - - $this->epubVersion = 0; - $nodes = $this->xpath->query('//opf:package[@unique-identifier="BookId"]'); - if ($nodes->length) { - $this->epubVersion = (int) static::getAttr($nodes, 'version'); - } else { - $nodes = $this->xpath->query('//opf:package'); - if ($nodes->length) { - $this->epubVersion = (int) static::getAttr($nodes, 'version'); - } - } - - return $this->epubVersion; - } - - /** - * file name getter - * @return string - */ - public function file() - { - return $this->file; - } - - /** - * meta file getter - * @return string - */ - public function meta() - { - return $this->meta; - } - - /** - * Close the epub file - * @return void - */ - public function close() - { - $this->zip->FileCancelModif($this->meta); - // TODO: Add cancelation of cover image - $this->zip->Close(); - } - - /** - * Remove iTunes files - * @return void - */ - public function cleanITunesCrap() - { - if ($this->zip->FileExists('iTunesMetadata.plist')) { - $this->zip->FileDelete('iTunesMetadata.plist'); - } - if ($this->zip->FileExists('iTunesArtwork')) { - $this->zip->FileDelete('iTunesArtwork'); - } - } - - /** - * Writes back all meta data changes - * @return void - */ - public function save() - { - $this->download(); - $this->zip->close(); - } - - /** - * Get the updated epub - * @param mixed $file - * @param bool $sendHeaders - * @return void - */ - public function download($file = false, $sendHeaders = true) - { - $this->zip->FileReplace($this->meta, $this->xml->saveXML()); - // add the cover image - if ($this->imagetoadd) { - $this->zip->FileAddPath($this->coverpath, $this->imagetoadd); - $this->imagetoadd = ''; - } - if ($file) { - $render = $this->zipClass::DOWNLOAD; - $this->zip->Flush($render, $file, static::MIME_TYPE, $sendHeaders); - } elseif ($this->zipClass == ZipEdit::class) { - $this->zip->SaveBeforeClose(); - } - } - - /** - * Get the components list as an array - * @return array<mixed> - */ - public function components() - { - $spine = []; - $nodes = $this->xpath->query('//opf:spine/opf:itemref'); - foreach ($nodes as $node) { - /** @var EpubDomElement $node */ - $idref = $node->getAttribute('idref'); - /** @var EpubDomElement $item */ - $item = $this->xpath->query('//opf:manifest/opf:item[@id="' . $idref . '"]')->item(0); - $spine[] = $this->encodeComponentName($item->getAttribute('href')); - } - return $spine; - } - - /** - * Get the component content - * @param mixed $comp - * @return mixed - */ - public function component($comp) - { - $path = $this->decodeComponentName($comp); - $path = $this->getFullPath($path); - if (!$this->zip->FileExists($path)) { - $status = $this->zip->FileGetState($path); - throw new Exception('Unable to find ' . $path . ' <' . $comp . '> = ' . $status); - } - - $data = $this->zip->FileRead($path); - return $data; - } - - /** - * Summary of getComponentName - * @param mixed $comp - * @param mixed $elementPath - * @return bool|string - */ - public function getComponentName($comp, $elementPath) - { - $path = $this->decodeComponentName($comp); - $path = $this->getFullPath($path, $elementPath); - if (!$this->zip->FileExists($path)) { - error_log('Unable to find ' . $path); - return false; - } - $ref = dirname('/' . $this->meta); - $ref = ltrim($ref, '\\'); - $ref = ltrim($ref, '/'); - if (strlen($ref) > 0) { - $path = str_replace($ref . '/', '', $path); - } - return $this->encodeComponentName($path); - } - - /** - * Encode the component name (to replace / and -) - * @param mixed $src - * @return string - */ - protected static function encodeComponentName($src) - { - return str_replace( - static::$encodeNameReplace[0], - static::$encodeNameReplace[1], - $src - ); - } - - /** - * Decode the component name (to replace / and -) - * @param mixed $src - * @return string - */ - protected static function decodeComponentName($src) - { - return str_replace( - static::$decodeNameReplace[0], - static::$decodeNameReplace[1], - $src - ); - } - - - /** - * Get the component content type - * @param mixed $comp - * @return string - */ - public function componentContentType($comp) - { - $comp = $this->decodeComponentName($comp); - $nodes = $this->xpath->query('//opf:manifest/opf:item[@href="' . $comp . '"]'); - if ($nodes->length) { - return static::getAttr($nodes, 'media-type'); - } - - // I had at least one book containing %20 instead of spaces in the opf file - $comp = str_replace(' ', '%20', $comp); - $nodes = $this->xpath->query('//opf:manifest/opf:item[@href="' . $comp . '"]'); - if ($nodes->length) { - return static::getAttr($nodes, 'media-type'); - } - return 'application/octet-stream'; - } - - /** - * Summary of getComponentSize - * @param mixed $comp - * @return bool|int - */ - public function getComponentSize($comp) - { - $path = $this->decodeComponentName($comp); - $path = $this->getFullPath($path); - if (!$this->zip->FileExists($path)) { - error_log('Unable to find ' . $path); - return false; - } - - $sizeMap = $this->loadSizeMap(); - return $sizeMap[$path]; - } - - /** - * EPUB 2 navigation control file (NCX format) - * See https://idpf.org/epub/20/spec/OPF_2.0_latest.htm#Section2.4.1 - * @param mixed $node - * @return array<string, string> - */ - protected function getNavPointDetail($node) - { - $title = $this->toc_xpath->query('x:navLabel/x:text', $node)->item(0)->nodeValue; - $nodes = $this->toc_xpath->query('x:content', $node); - $src = static::getAttr($nodes, 'src'); - $src = $this->encodeComponentName($src); - $item = ['title' => preg_replace('~[\r\n]+~', '', $title), 'src' => $src]; - $insidenodes = $this->toc_xpath->query('x:navPoint', $node); - if (count($insidenodes) < 1) { - return $item; - } - $item['children'] = []; - foreach ($insidenodes as $insidenode) { - $item['children'][] = $this->getNavPointDetail($insidenode); - } - return $item; - } - - /** - * EPUB 3 navigation document (toc nav element) - * See https://www.w3.org/TR/epub-33/#sec-nav-toc - * @param mixed $node - * @return array<string, string> - */ - protected function getNavTocListItem($node) - { - $nodes = $this->nav_xpath->query('x:a', $node); - $title = $nodes->item(0)->nodeValue; - $src = static::getAttr($nodes, 'href'); - $src = $this->encodeComponentName($src); - $item = ['title' => preg_replace('~[\r\n]+~', '', $title), 'src' => $src]; - $insidenodes = $this->nav_xpath->query('x:ol/x:li', $node); - if (count($insidenodes) < 1) { - return $item; - } - $item['children'] = []; - foreach ($insidenodes as $insidenode) { - $item['children'][] = $this->getNavTocListItem($insidenode); - } - return $item; - } - - /** - * Get the Epub content (TOC) as an array - * - * For each chapter there is a "title" and a "src", and optional "children" - * See https://github.com/joseph/Monocle/wiki/Book-data-object for details - * @return mixed - */ - public function contents() - { - $contents = []; - if (!empty($this->nav)) { - $toc = $this->nav_xpath->query('//x:nav[@epub:type="toc"]')->item(0); - $nodes = $this->nav_xpath->query('x:ol/x:li', $toc); - foreach ($nodes as $node) { - $contents[] = $this->getNavTocListItem($node); - } - return $contents; - } - $nodes = $this->toc_xpath->query('//x:ncx/x:navMap/x:navPoint'); - foreach ($nodes as $node) { - $contents[] = $this->getNavPointDetail($node); - } - return $contents; - } - - /** - * 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', - * ) - * - * @param mixed $authors - * @return void - */ - public function setAuthors($authors) - { - // Author where given as a comma separated list - if (is_string($authors)) { - if ($authors == '') { - $authors = []; - } else { - $authors = explode(',', $authors); - $authors = array_map('trim', $authors); - } - } - - // delete existing nodes - $nodes = $this->xpath->query('//opf:metadata/dc:creator[@opf:role="aut"]'); - static::deleteNodes($nodes); - - // add new nodes - /** @var EpubDomElement $parent */ - $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->setAttrib('opf:role', 'aut'); - $node->setAttrib('opf:file-as', $as); - } - - $this->reparse(); - } - - /** - * Get the book author(s) - * @return array<string> - */ - public function getAuthors() - { - $rolefix = false; - $authors = []; - $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) { - /** @var EpubDomElement $node */ - $name = $node->nodeValue; - $as = $node->getAttrib('opf:file-as'); - if (!$as) { - $as = $name; - $node->setAttrib('opf:file-as', $as); - } - if ($rolefix) { - $node->setAttrib('opf:role', 'aut'); - } - $authors[$as] = $name; - } - return $authors; - } - - /** - * Set or get the Google Books ID - * - * @param string|bool $google - * @return mixed - */ - 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|bool $amazon - * @return mixed - */ - public function Amazon($amazon = false) - { - return $this->getset('dc:identifier', $amazon, 'opf:scheme', 'AMAZON'); - } - - /** - * Set the Series of the book - * - * @param string $serie - * @return void - */ - public function setSeries($serie) - { - $this->setMetaDestination('opf:meta', 'name', 'calibre:series', 'content', $serie); - } - - /** - * Get the Series of the book - * - * @return mixed - */ - public function getSeries() - { - return $this->getMetaDestination('opf:meta', 'name', 'calibre:series', 'content'); - } - - /** - * Set the Series Index of the book - * - * @param string $seriesIndex - * @return void - */ - public function setSeriesIndex($seriesIndex) - { - $this->setMetaDestination('opf:meta', 'name', 'calibre:series_index', 'content', $seriesIndex); - } - - /** - * Get the Series Index of the book - * - * @return mixed - */ - public function getSeriesIndex() - { - return $this->getMetaDestination('opf:meta', 'name', 'calibre:series_index', 'content'); - } - - /** - * Set the book's subjects (aka. tags) - * - * Subject should be given as array, but a comma separated string will also - * be accepted. - * - * @param array<string>|string $subjects - * @return void - */ - public function setSubjects($subjects) - { - if (is_string($subjects)) { - if ($subjects === '') { - $subjects = []; - } else { - $subjects = explode(',', $subjects); - $subjects = array_map('trim', $subjects); - } - } - - // delete previous - $nodes = $this->xpath->query('//opf:metadata/dc:subject'); - static::deleteNodes($nodes); - // 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(); - } - - /** - * Get the book's subjects (aka. tags) - * @return array<mixed> - */ - public function getSubjects() - { - $subjects = []; - $nodes = $this->xpath->query('//opf:metadata/dc:subject'); - foreach ($nodes as $node) { - $subjects[] = $node->nodeValue; - } - return $subjects; - } - - /** - * Update the cover data - * - * 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 void - */ - public function setCoverInfo($path, $mime) - { - // remove current pointer - $nodes = $this->xpath->query('//opf:metadata/opf:meta[@name="cover"]'); - static::deleteNodes($nodes); - // remove previous manifest entries if they where made by us - $nodes = $this->xpath->query('//opf:manifest/opf:item[@id="' . static::COVER_ID . '"]'); - static::deleteNodes($nodes); - - // add pointer - /** @var EpubDomElement $parent */ - $parent = $this->xpath->query('//opf:metadata')->item(0); - $node = $parent->newChild('opf:meta'); - $node->setAttrib('opf:name', 'cover'); - $node->setAttrib('opf:content', static::COVER_ID); - - // add manifest - /** @var EpubDomElement $parent */ - $parent = $this->xpath->query('//opf:manifest')->item(0); - $node = $parent->newChild('opf:item'); - $node->setAttrib('id', static::COVER_ID); - $node->setAttrib('opf:href', static::COVER_ID . '.img'); - $node->setAttrib('opf:media-type', $mime); - - // remember path for save action - $this->imagetoadd = $path; - $this->coverpath = $this->getFullPath(static::COVER_ID . '.img'); - - $this->reparse(); - } - - /** - * 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. - * - * @return array<mixed> - */ - public function getCoverInfo() - { - $item = $this->getCoverItem(); - if (!$item) { - return $this->no_cover(); - } - - $mime = $item->getAttrib('opf:media-type'); - $path = $item->getAttrib('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 [ - 'mime' => $mime, - 'data' => $data, - 'found' => $path, - ]; - } - - /** - * Summary of getCoverId - * @return string|null - */ - public function getCoverId() - { - $nodes = $this->xpath->query('//opf:metadata/opf:meta[@name="cover"]'); - if (!$nodes->length) { - return null; - } - - $coverid = (string) static::getAttr($nodes, 'opf:content'); - if (!$coverid) { - return null; - } - - return $coverid; - } - - /** - * Summary of getCoverItem - * @return EpubDomElement|null - */ - public function getCoverItem() - { - $coverid = $this->getCoverId(); - if (!$coverid) { - return null; - } - - $nodes = $this->xpath->query('//opf:manifest/opf:item[@id="' . $coverid . '"]'); - if (!$nodes->length) { - return null; - } - - /** @var EpubDomElement $node */ - $node = $nodes->item(0); - return $node; - } - - /** - * Get the internal path of the cover image file. - * - * @return string|null - */ - public function getCoverPath() - { - $item = $this->getCoverItem(); - if (!$item) { - return null; - } - - return $item->getAttrib('opf:href'); - } - - /** - * Summary of Combine - * @param mixed $a - * @param mixed $b - * @throws \InvalidArgumentException - * @return string - */ - public static function Combine($a, $b) - { - $isAbsolute = false; - if ($a[0] == '/') { - $isAbsolute = true; - } - - if ($b[0] == '/') { - throw new InvalidArgumentException('Second path part must not start with /'); - } - - $splittedA = preg_split('#/#', $a); - $splittedB = preg_split('#/#', $b); - - $pathParts = []; - $mergedPath = array_merge($splittedA, $splittedB); - - foreach ($mergedPath as $item) { - if ($item == null || $item == '' || $item == '.') { - continue; - } - - if ($item == '..') { - array_pop($pathParts); - continue; - } - - array_push($pathParts, $item); - } - - $path = implode('/', $pathParts); - if ($isAbsolute) { - return('/' . $path); - } else { - return($path); - } - } - - /** - * Summary of getFullPath - * @param mixed $file - * @param mixed $context - * @return string - */ - protected function getFullPath($file, $context = null) - { - $path = dirname('/' . $this->meta) . '/' . $file; - $path = ltrim($path, '\\'); - $path = ltrim($path, '/'); - if (!empty($context)) { - $path = $this->combine(dirname($path), $context); - } - //error_log ("FullPath : $path ($file / $context)"); - return $path; - } - - /** - * Summary of updateForKepub - * @return void - */ - public function updateForKepub() - { - $item = $this->getCoverItem(); - if (!is_null($item)) { - $item->setAttrib('opf:properties', 'cover-image'); - } - } - - /** - * Summary of setCoverFile - * @param string $path - * @param string $mime - * @return array<mixed>|void - */ - public function setCoverFile($path, $mime) - { - $hascover = true; - $item = $this->getCoverItem(); - if (is_null($item)) { - $hascover = false; - return; // TODO For now only update - } else { - $mime = $item->getAttrib('opf:media-type'); - $this->coverpath = $item->getAttrib('opf:href'); - $this->coverpath = dirname('/' . $this->meta) . '/' . $this->coverpath; // image path is relative to meta file - $this->coverpath = ltrim($this->coverpath, '\\'); - $this->coverpath = ltrim($this->coverpath, '/'); - } - - // set cover - - $item->setAttrib('opf:media-type', $mime); - - // remember path for save action - $this->imagetoadd = $path; - - $this->reparse(); - - // not very useful here, but data gets added in download() if needed - return [ - 'data' => null, - 'mime' => $mime, - 'found' => $this->coverpath, - ]; - } - - /** - * Summary of getAttr - * @param DOMNodeList<EpubDomElement> $nodes list of Element items - * @param string $att Attribute name - * @return string - */ - protected static function getAttr($nodes, $att) - { - $node = $nodes->item(0); - return $node->getAttrib($att); - } - - /** - * Summary of deleteNodes - * @param DOMNodeList<EpubDomElement> $nodes list of Element items - * @return void - */ - protected static function deleteNodes($nodes) - { - foreach ($nodes as $node) { - $node->delete(); - } - } - - /** - * 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|bool $value New node value - * @param string|bool $att Attribute name - * @param string|bool|array<mixed> $aval Attribute value - * @param string|bool $datt Destination attribute - * @return string|void - */ - protected function getset($item, $value = false, $att = false, $aval = false, $datt = false) - { - // construct xpath - $xpath = '//opf:metadata/' . $item; - if ($att) { - if (is_array($aval)) { - $xpath .= '[@' . $att . '="'; - $xpath .= implode("\" or @$att=\"", $aval); - $xpath .= '"]'; - } else { - $xpath .= '[@' . $att . '="' . $aval . '"]'; - } - } - - // set value - if ($value !== false) { - $value = htmlspecialchars($value); - $nodes = $this->xpath->query($xpath); - if ($nodes->length == 1) { - /** @var EpubDomElement $node */ - $node = $nodes->item(0); - if ($value === '') { - // the user want's to empty this value -> delete the node - $node->delete(); - } else { - // replace value - if ($datt) { - $node->setAttrib($datt, $value); - } else { - $node->nodeValue = $value; - } - } - } else { - // if there are multiple matching nodes for some reason delete - // them. we'll replace them all with our own single one - static::deleteNodes($nodes); - // readd them - if ($value) { - /** @var EpubDomElement $parent */ - $parent = $this->xpath->query('//opf:metadata')->item(0); - - $node = $parent->newChild($item); - if ($att) { - $node->setAttrib($att, $aval); - } - if ($datt) { - $node->setAttrib($datt, $value); - } else { - $node->nodeValue = $value; - } - } - } - - $this->reparse(); - } - - // get value - $nodes = $this->xpath->query($xpath); - if ($nodes->length) { - /** @var EpubDomElement $node */ - $node = $nodes->item(0); - if ($datt) { - return $node->getAttrib($datt); - } else { - return $node->nodeValue; - } - } else { - return ''; - } - } - - /** - * Return a not found response for Cover() - * @return array<string, mixed> - */ - protected function no_cover() - { - return [ - '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 - * @return void - */ - protected function reparse() - { - $this->xml->loadXML($this->xml->saveXML()); - $this->xpath = new EpubDomXPath($this->xml); - // reset structural members - $this->manifest = null; - $this->spine = null; - $this->tocnav = null; - } - - /** based on slightly more updated version at https://github.com/epubli/epub */ - - /** - * A simple 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 - * @param string $value New node value - * @param bool|string $attribute Attribute name - * @param bool|string $attributeValue Attribute value - * @param bool $caseSensitive - * @return mixed - */ - protected function setMeta($item, $value, $attribute = false, $attributeValue = false, $caseSensitive = true) - { - /** - if ($attributeValue !== false && !$caseSensitive) { - $attval = is_array($attributeValue) ? $attributeValue : [ $attributeValue ]; - $vallist = []; - foreach ($attval as $val) { - $vallist[] = strtoupper($val); - $vallist[] = strtolower($val); - } - $attributeValue = $vallist; - } - */ - return $this->getset($item, $value, $attribute, $attributeValue); - } - - /** - * A simple getter for simple meta attributes - * - * It should only be used for attributes that are expected to be unique - * - * @param string $item XML node to get - * @param bool|string $att Attribute name - * @param bool|string $aval Attribute value - * @param bool $caseSensitive - * @return string - */ - protected function getMeta($item, $att = false, $aval = false, $caseSensitive = true) - { - /** - if ($aval !== false && !$caseSensitive) { - $attval = is_array($aval) ? $aval : [ $aval ]; - $vallist = []; - foreach ($attval as $val) { - $vallist[] = strtoupper($val); - $vallist[] = strtolower($val); - } - $aval = $vallist; - } - */ - return $this->getset($item, false, $att, $aval); - } - - /** - * A simple setter for simple meta attributes - with destination attribute (for Serie) - * - * It should only be used for attributes that are expected to be unique - * - * @param string $item XML node to set - * @param string $attribute Attribute name - * @param string $attributeValue Attribute value - * @param string $datt Destination attribute - * @param string $value New node value - * @return mixed - */ - protected function setMetaDestination($item, $attribute, $attributeValue, $datt, $value) - { - return $this->getset($item, $value, $attribute, $attributeValue, $datt); - } - - /** - * A simple getter for simple meta attributes - with destination attribute (for Serie) - * - * It should only be used for attributes that are expected to be unique - * - * @param string $item XML node to get - * @param string $att Attribute name - * @param string $aval Attribute value - * @param string $datt Destination attribute - * @return string - */ - protected function getMetaDestination($item, $att, $aval, $datt) - { - return $this->getset($item, false, $att, $aval, $datt); - } - - /** - * Set the book title - * - * @param string $title - * @return mixed - */ - public function setTitle($title) - { - return $this->getset('dc:title', $title); - } - - /** - * Get the book title - * - * @return mixed - */ - public function getTitle() - { - return $this->getset('dc:title'); - } - - /** - * Set the book's language - * - * @param string $lang - * @return mixed - */ - public function setLanguage($lang) - { - return $this->getset('dc:language', $lang); - } - - /** - * Get the book's language - * - * @return mixed - */ - public function getLanguage() - { - return $this->getset('dc:language'); - } - - /** - * Set the book's publisher info - * - * @param string $publisher - * @return void - */ - public function setPublisher($publisher) - { - $this->setMeta('dc:publisher', $publisher); - } - - /** - * Get the book's publisher info - * - * @return string - */ - public function getPublisher() - { - return $this->getMeta('dc:publisher'); - } - - /** - * Set the book's copyright info - * - * @param string $rights - * @return void - */ - public function setCopyright($rights) - { - $this->setMeta('dc:rights', $rights); - } - - /** - * Get the book's copyright info - * - * @return string - */ - public function getCopyright() - { - return $this->getMeta('dc:rights'); - } - - /** - * Set the book's description - * - * @param string $description - * @return void - */ - public function setDescription($description) - { - $this->setMeta('dc:description', $description); - } - - /** - * Get the book's description - * - * @return string - */ - public function getDescription() - { - return $this->getMeta('dc:description'); - } - - /** - * Set a date for an event in the package file’s meta section. - * - * @param string $event - * @param string $date Date eg: 2012-05-19T12:54:25Z - * @return void - */ - public function setEventDate($event, $date) - { - $this->getset('dc:date', $date, 'opf:event', $event); - } - - /** - * Get a date for an event in the package file’s meta section. - * - * @param string $event - * @return mixed - */ - public function getEventDate($event) - { - $res = $this->getset('dc:date', false, 'opf:event', $event); - - return $res; - } - - /** - * Set the book's creation date - * - * @param string $date Date eg: 2012-05-19T12:54:25Z - * @return void - */ - public function setCreationDate($date) - { - $this->setEventDate('creation', $date); - } - - /** - * Get the book's creation date - * - * @return mixed - */ - public function getCreationDate() - { - $res = $this->getEventDate('creation'); - - return $res; - } - - /** - * Set the book's modification date - * - * @param string $date Date eg: 2012-05-19T12:54:25Z - * @return void - */ - public function setModificationDate($date) - { - $this->setEventDate('modification', $date); - } - - /** - * Get the book's modification date - * - * @return mixed - */ - public function getModificationDate() - { - $res = $this->getEventDate('modification'); - - return $res; - } - - /** - * Set an identifier in the package file’s meta section. - * - * @param string|array<string> $idScheme The identifier’s scheme. If an array is given - * all matching identifiers are replaced by one with the first value as scheme. - * @param string $value - * @param bool $caseSensitive - * @return void - */ - public function setIdentifier($idScheme, $value, $caseSensitive = false) - { - $this->setMeta('dc:identifier', $value, 'opf:scheme', $idScheme, $caseSensitive); - } - - /** - * Set an identifier from the package file’s meta section. - * - * @param string|array<string> $idScheme The identifier’s scheme. If an array is given - * the scheme can be any of its values. - * @param bool $caseSensitive - @todo changed to true here - * @return string The value of the first matching element. - */ - public function getIdentifier($idScheme, $caseSensitive = true) - { - return $this->getMeta('dc:identifier', 'opf:scheme', $idScheme, $caseSensitive); - } - - /** - * Set the book's unique identifier - * - * @param string $value - * @return void - */ - public function setUniqueIdentifier($value) - { - //$nodes = $this->xpath->query('/opf:package'); - $idRef = $this->xpath->document->documentElement->getAttribute('unique-identifier'); - $this->setMeta('dc:identifier', $value, 'id', $idRef); - } - - /** - * Get the book's unique identifier - * - * @param bool $normalize - * @return string - */ - public function getUniqueIdentifier($normalize = false) - { - //$nodes = $this->xpath->query('/opf:package'); - $idRef = $this->xpath->document->documentElement->getAttribute('unique-identifier'); - $idVal = $this->getMeta('dc:identifier', 'id', $idRef); - if ($normalize) { - $idVal = strtolower($idVal); - $idVal = str_replace('urn:uuid:', '', $idVal); - } - - return $idVal; - } - - /** - * Set the book's UUID - @todo pick one + case sensitive - * - * @param string $uuid - * @return void - */ - public function setUuid($uuid) - { - //$this->setIdentifier(['UUID', 'uuid', 'URN', 'urn'], $uuid); - $this->setIdentifier('URN', $uuid); - } - - /** - * Get the book's UUID - @todo pick one + case sensitive - * - * @return string - */ - public function getUuid() - { - //return $this->getIdentifier(['uuid', 'urn']); - return $this->getIdentifier(['UUID', 'URN']); - } - - /** - * Set the book's URI - * - * @param string $uri - * @return void - */ - public function setUri($uri) - { - $this->setIdentifier('URI', $uri); - } - - /** - * Get the book's URI - * - * @return string - */ - public function getUri() - { - return $this->getIdentifier('URI'); - } - - /** - * Set the book's ISBN - * - * @param string $isbn - * @return void - */ - public function setIsbn($isbn) - { - $this->setIdentifier('ISBN', $isbn); - } - - /** - * Get the book's ISBN - * - * @return string - */ - public function getIsbn() - { - return $this->getIdentifier('ISBN'); - } - - /** - * Set the Calibre UUID of the book - * - * @param string $uuid - * @return void - */ - public function setCalibre($uuid) - { - $this->setIdentifier('calibre', $uuid); - } - - /** - * Get the Calibre UUID of the book - * - * @return string - */ - public function getCalibre() - { - return $this->getIdentifier('calibre'); - } - - /** - * Remove the cover image - * - * 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. - * @return void - */ - public function clearCover() - { - if (!$this->hasCover()) { - return; - } - - $manifest = $this->getManifest(); - - // remove any cover image file added by us - if (isset($manifest[static::COVER_ID])) { - $name = $this->getFullPath(static::COVER_ID . '.img'); - if (!$this->zip->FileDelete($name)) { - throw new Exception('Unable to remove ' . $name); - } - } - - // remove metadata cover pointer - $nodes = $this->xpath->query('//opf:metadata/opf:meta[@name="cover"]'); - static::deleteNodes($nodes); - - // remove previous manifest entries if they where made by us - $nodes = $this->xpath->query('//opf:manifest/opf:item[@id="' . static::COVER_ID . '"]'); - static::deleteNodes($nodes); - - $this->reparse(); - } - - /** - * Set the cover image - * - * @param string $path local filesystem path to a new cover image - * @param string $mime mime type of the given file - * @return void - */ - public function setCover($path, $mime) - { - if (!$path) { - throw new InvalidArgumentException('Parameter $path must not be empty!'); - } - - if (!is_readable($path)) { - throw new InvalidArgumentException("Cannot add $path as new cover image since that file is not readable!"); - } - - $this->clearCover(); - - // add metadata cover pointer - /** @var EpubDomElement $parent */ - $parent = $this->xpath->query('//opf:metadata')->item(0); - $node = $parent->newChild('opf:meta'); - $node->setAttrib('opf:name', 'cover'); - $node->setAttrib('opf:content', static::COVER_ID); - - // add manifest item - /** @var EpubDomElement $parent */ - $parent = $this->xpath->query('//opf:manifest')->item(0); - $node = $parent->newChild('opf:item'); - $node->setAttrib('id', static::COVER_ID); - $node->setAttrib('opf:href', static::COVER_ID . '.img'); - $node->setAttrib('opf:media-type', $mime); - - // add the cover image - $name = $this->getFullPath(static::COVER_ID . '.img'); - if (!$this->zip->FileAddPath($name, $path)) { - throw new Exception('Unable to add ' . $name); - } - - $this->reparse(); - } - - /** - * Get the cover image - * - * @return string|null The binary image data or null if no image exists. - */ - public function getCover() - { - $comp = $this->getCoverPath(); - - return $comp ? $this->component($comp) : null; - } - - /** - * Whether a cover image meta entry does exist. - * - * @return bool - */ - public function hasCover() - { - return !empty($this->getCoverId()); - } - - /** - * Add a title page with the cover image to the EPUB. - * - * @param string $templatePath The path to the template file. Defaults to an XHTML file contained in this library. - * @return void - */ - public function addCoverImageTitlePage($templatePath = __DIR__ . '/../templates/titlepage.xhtml') - { - $xhtmlFilename = static::TITLE_PAGE_ID . '.xhtml'; - - // add title page file to zip - $template = file_get_contents($templatePath); - $xhtml = strtr($template, ['{{ title }}' => $this->getTitle(), '{{ coverPath }}' => $this->getCoverPath()]); - $name = $this->getFullPath($xhtmlFilename); - if (!$this->zip->FileReplace($name, $xhtml)) { - throw new Exception('Unable to replace ' . $name); - } - - // prepend title page file to manifest - $parent = $this->xpath->query('//opf:manifest')->item(0); - $node = new EpubDomElement('opf:item'); - $parent->insertBefore($node, $parent->firstChild); - $node->setAttrib('id', static::TITLE_PAGE_ID); - $node->setAttrib('opf:href', $xhtmlFilename); - $node->setAttrib('opf:media-type', 'application/xhtml+xml'); - - // prepend title page spine item - $parent = $this->xpath->query('//opf:spine')->item(0); - $node = new EpubDomElement('opf:itemref'); - $parent->insertBefore($node, $parent->firstChild); - $node->setAttrib('idref', static::TITLE_PAGE_ID); - - // prepend title page guide reference - $parent = $this->xpath->query('//opf:guide')->item(0); - $node = new EpubDomElement('opf:reference'); - $parent->insertBefore($node, $parent->firstChild); - $node->setAttrib('opf:href', $xhtmlFilename); - $node->setAttrib('opf:type', 'cover'); - $node->setAttrib('opf:title', 'Title Page'); - } - - /** - * Remove the title page added by this library (determined by a certain manifest item ID). - * @return void - */ - public function removeTitlePage() - { - $xhtmlFilename = static::TITLE_PAGE_ID . '.xhtml'; - - // remove title page file from zip - $name = $this->getFullPath($xhtmlFilename); - if (!$this->zip->FileDelete($name)) { - throw new Exception('Unable to remove ' . $name); - } - - // remove title page file from manifest - $nodes = $this->xpath->query('//opf:manifest/opf:item[@id="' . static::TITLE_PAGE_ID . '"]'); - static::deleteNodes($nodes); - - // remove title page spine item - $nodes = $this->xpath->query('//opf:spine/opf:itemref[@idref="' . static::TITLE_PAGE_ID . '"]'); - static::deleteNodes($nodes); - - // remove title page guide reference - $nodes = $this->xpath->query('//opf:guide/opf:reference[@href="' . $xhtmlFilename . '"]'); - static::deleteNodes($nodes); - } - - /** - * Get the Calibre book annotations from opf:metadata (if saved) - * @param ?string $data - * @return array<mixed> - */ - public function getCalibreAnnotations($data = null) - { - if (!empty($data)) { - $this->loadXmlData($data); - } - $annotations = []; - $nodes = $this->xpath->query('//opf:metadata/opf:meta[@name="calibre:annotation"]'); - if ($nodes->length == 0) { - return $annotations; - } - foreach ($nodes as $node) { - /** @var EpubDomElement $node */ - $content = $node->getAttribute('content'); - try { - $annotations[] = json_decode($content, true, 512, JSON_THROW_ON_ERROR); - } catch (JsonException $e) { - $annotations[] = $content; - } - } - return $annotations; - } - - /** - * Get the Calibre bookmarks from META-INF/calibre_bookmarks.txt (if saved) - * @param ?string $data - * @return array<mixed> - */ - public function getCalibreBookmarks($data = null) - { - if (empty($data)) { - if (!$this->zip->FileExists(static::BOOKMARK_FILE)) { - throw new Exception('Unable to find ' . static::BOOKMARK_FILE); - } - $data = $this->zip->FileRead(static::BOOKMARK_FILE); - if ($data == false) { - throw new Exception('Failed to access epub bookmark file'); - } - } - if (!str_starts_with($data, static::EPUB_FILE_TYPE_MAGIC)) { - throw new Exception('Invalid format for epub bookmark file'); - } - $content = substr($data, strlen(static::EPUB_FILE_TYPE_MAGIC)); - $content = base64_decode($content); - try { - $bookmarks = json_decode($content, true, 512, JSON_THROW_ON_ERROR); - } catch (JsonException $e) { - $bookmarks = $content; - } - return $bookmarks; - } - - /** - * Get the manifest of this EPUB. - * - * @return Manifest - * @throws Exception - */ - public function getManifest() - { - if ($this->manifest) { - return $this->manifest; - } - - /** @var EpubDomElement|null $manifestNode */ - $manifestNode = $this->xpath->query('//opf:manifest')->item(0); - if (is_null($manifestNode)) { - throw new Exception('No manifest element found in EPUB!'); - } - - $this->manifest = new Manifest(); - /** @var EpubDomElement $item */ - foreach ($manifestNode->getElementsByTagName('item') as $item) { - $id = $item->getAttribute('id'); - $href = urldecode($item->getAttribute('href')); - $fullPath = $this->getFullPath($href); - // this won't work with clsTbsZip - $this->zip->getStream($fullPath); - //$handle = $this->zip->FileStream($fullPath); - $callable = function () use ($fullPath) { - // Automatic binding of $this - return $this->zip->FileRead($fullPath); - }; - $size = $this->zipSizeMap[$fullPath] ?? 0; - $mediaType = $item->getAttribute('media-type'); - $this->manifest->createItem($id, $href, $callable, $size, $mediaType); - } - - return $this->manifest; - } - - /** - * Get the spine structure of this EPUB. - * - * @return Spine - * @throws Exception - */ - public function getSpine() - { - if ($this->spine) { - return $this->spine; - } - - $nodes = $this->xpath->query('//opf:spine'); - if (!$nodes->length) { - throw new Exception('No spine element found in EPUB!'); - } - $tocId = static::getAttr($nodes, 'toc'); - $tocFormat = Toc::class; - if (empty($tocId)) { - $nodes = $this->xpath->query('//opf:manifest/opf:item[@properties="nav"]'); - $tocId = static::getAttr($nodes, 'id'); - $tocFormat = Nav::class; - } - - $manifest = $this->getManifest(); - - if (!isset($manifest[$tocId])) { - throw new Exception('TOC or NAV item referenced in spine missing in manifest!'); - } - - $this->spine = new Spine($manifest[$tocId], $tocFormat); - - /** @var EpubDomElement $spineNode */ - $spineNode = $this->xpath->query('//opf:spine')->item(0); - - $itemRefNodes = $spineNode->getElementsByTagName('itemref'); - foreach ($itemRefNodes as $itemRef) { - /** @var EpubDomElement $itemRef */ - $id = $itemRef->getAttribute('idref'); - if (!isset($manifest[$id])) { - throw new Exception("Item $id referenced in spine missing in manifest!"); - } - // Link the item from the manifest to the spine. - $this->spine->appendItem($manifest[$id]); - } - - return $this->spine; - } - - /** - * Get the table of contents structure of this EPUB. - * - * @return Toc|Nav - * @throws Exception - */ - public function getToc() - { - if ($this->tocnav) { - return $this->tocnav; - } - - // @todo support Nav structure as well, see initSpineComponent - if ($this->getSpine()->getTocFormat() === Nav::class) { - throw new Exception('TODO: support NAV structure as well'); - } - - $tocpath = $this->getFullPath($this->getSpine()->getTocItem()->getHref()); - $data = $this->zip->FileRead($tocpath); - $toc = new DOMDocument(); - $toc->registerNodeClass(DOMElement::class, EpubDomElement::class); - $toc->loadXML($data); - $xpath = new EpubDomXPath($toc); - //$rootNamespace = $toc->lookupNamespaceUri($toc->namespaceURI); - //$xpath->registerNamespace('x', $rootNamespace); - - $titleNode = $xpath->query('//ncx:docTitle/ncx:text')->item(0); - $title = $titleNode ? $titleNode->nodeValue : ''; - $authorNode = $xpath->query('//ncx:docAuthor/ncx:text')->item(0); - $author = $authorNode ? $authorNode->nodeValue : ''; - $this->tocnav = new Toc($title, $author); - - $navPointNodes = $xpath->query('//ncx:navMap/ncx:navPoint'); - - $this->loadNavPoints($navPointNodes, $this->tocnav->getNavMap(), $xpath); - - return $this->tocnav; - } - - /** - * Load navigation points from TOC XML DOM into TOC object structure. - * - * @param DOMNodeList<EPubDomElement> $navPointNodes List of nodes to load from. - * @param TocNavPointList $navPointList List structure to load into. - * @param EpubDomXPath $xp The XPath of the TOC document. - * @return void - */ - protected static function loadNavPoints(DOMNodeList $navPointNodes, TocNavPointList $navPointList, EpubDomXPath $xp) - { - foreach ($navPointNodes as $navPointNode) { - /** @var EpubDomElement $navPointNode */ - $id = $navPointNode->getAttribute('id'); - $class = $navPointNode->getAttribute('class'); - $playOrder = (int) $navPointNode->getAttribute('playOrder'); - $labelTextNode = $xp->query('ncx:navLabel/ncx:text', $navPointNode)->item(0); - $label = $labelTextNode ? $labelTextNode->nodeValue : ''; - /** @var EpubDomElement|null $contentNode */ - $contentNode = $xp->query('ncx:content', $navPointNode)->item(0); - $contentSource = $contentNode ? $contentNode->getAttribute('src') : ''; - $navPoint = new TocNavPoint($id, $class, $playOrder, $label, $contentSource); - $navPointList->append($navPoint); - $childNavPointNodes = $xp->query('ncx:navPoint', $navPointNode); - $childNavPoints = $navPoint->getChildren(); - - static::loadNavPoints($childNavPointNodes, $childNavPoints, $xp); - } - } - - /** - * Summary of getNav - * @return Toc|Nav - */ - public function getNav() - { - if ($this->tocnav) { - return $this->tocnav; - } - - $navpath = $this->getFullPath($this->getSpine()->getTocItem()->getHref()); - $data = $this->zip->FileRead($navpath); - $nav = new DOMDocument(); - $nav->registerNodeClass(DOMElement::class, EpubDomElement::class); - $nav->loadXML($data); - $xpath = new EpubDomXPath($nav); - $rootNamespace = $nav->lookupNamespaceUri($nav->namespaceURI); - $xpath->registerNamespace('x', $rootNamespace); - - // nav documents don't contain mandatory title or author - look in main doc - $title = $this->getTitle(); - $author = implode(', ', $this->getAuthors()); - $this->tocnav = new Nav($title, $author); - - $toc = $xpath->query('//x:nav[@epub:type="toc"]')->item(0); - $navListNodes = $xpath->query('x:ol/x:li', $toc); - if ($navListNodes->length > 0) { - $this->loadNavList($navListNodes, $this->tocnav->getNavMap(), $xpath); - } - - return $this->tocnav; - } - - /** - * Load navigation points from NAV XML DOM into NAV object structure. - * - * @param DOMNodeList<EPubDomElement> $navListNodes List of nodes to load from. - * @param TocNavPointList $navPointList List structure to load into. - * @param EpubDomXPath $xp The XPath of the NAV document. - * @param int $depth Current depth of this list (recursive) - * @param int $order Current start order for this list - * @return void - */ - protected static function loadNavList(DOMNodeList $navListNodes, TocNavPointList $navPointList, EpubDomXPath $xp, int $depth = 0, int $order = 0) - { - // h1 - h6 are supported as title for the list - $className = 'h' . strval($depth + 1); - if ($depth > 5) { - throw new Exception("We're at maximum depth for NAV DOC here!?"); - } - foreach ($navListNodes as $navPointNode) { - $order += 1; - $nodes = $xp->query('x:a', $navPointNode); - $label = trim($nodes->item(0)->nodeValue); - if (empty($label)) { - // do we have an image with title or alt available? - $images = $xp->query('x:a/x:img', $navPointNode); - if ($images->length) { - $label = static::getAttr($images, 'alt'); - if (empty($label)) { - $label = static::getAttr($images, 'title'); - } - } - } - $contentSource = static::getAttr($nodes, 'href'); - /** @var EpubDomElement $navPointNode */ - $id = $navPointNode->getAttribute('id'); - $class = $className; - $playOrder = $order; - $navPoint = new TocNavPoint($id, $class, $playOrder, $label, $contentSource); - $navPointList->append($navPoint); - $childNavPointNodes = $xp->query('x:ol/x:li', $navPointNode); - $childNavPoints = $navPoint->getChildren(); - - static::loadNavList($childNavPointNodes, $childNavPoints, $xp, $depth + 1, $order); - } - } - - /** - * Extract the contents of this EPUB. - * - * This concatenates contents of items according to their order in the spine. - * - * @param bool $keepMarkup Whether to keep the XHTML markup rather than extracted plain text. - * @param float $fraction If less than 1, only the respective part from the beginning of the book is extracted. - * @return string The contents of this EPUB. - * @throws Exception - */ - public function getContents($keepMarkup = false, $fraction = 1.0) - { - $contents = ''; - if ($fraction < 1) { - $totalSize = 0; - foreach ($this->getSpine() as $item) { - $totalSize += $item->getSize(); - } - $fractionSize = $totalSize * $fraction; - $contentsSize = 0; - foreach ($this->spine as $item) { - $itemSize = $item->getSize(); - if ($contentsSize + $itemSize > $fractionSize) { - break; - } - $contentsSize += $itemSize; - $contents .= $item->getContents(null, null, $keepMarkup); - } - } else { - foreach ($this->getSpine() as $item) { - $contents .= $item->getContents(null, null, $keepMarkup); - } - } - - return $contents; - } - - /** - * Build an XPath expression to select certain nodes in the metadata section. - * - * @param string $element The node name of the elements to select. - * @param string $attribute If set, the attribute required in the element. - * @param string|array<string> $value If set, the value of the above named attribute. If an array is given - * all of its values will be allowed in the selector. - * @param bool $caseSensitive If false, attribute values are matched case insensitively. - * (This is not completely true, as only full upper or lower case strings are matched, not mixed case. - * A lower-case function is missing in XPath 1.0.) - * @return string - */ - protected static function buildMetaXPath($element, $attribute, $value, $caseSensitive = true) - { - $xpath = '//opf:metadata/' . $element; - if ($attribute) { - $xpath .= "[@$attribute"; - if ($value) { - $values = is_array($value) ? $value : [$value]; - if (!$caseSensitive) { - $temp = []; - foreach ($values as $item) { - $temp[] = strtolower($item); - $temp[] = strtoupper($item); - } - $values = $temp; - } - - $xpath .= '="'; - $xpath .= implode("\" or @$attribute=\"", $values); - $xpath .= '"'; - } - $xpath .= ']'; - } - - return $xpath; - } - - /** - * Load an XML file from the EPUB/ZIP archive into a new XPath object. - * - * @param string $path The XML file to load from the ZIP archive. - * @return EpubDomXPath The XPath representation of the XML file. - * @throws Exception If the given path could not be read. - */ - protected function loadXPathFromItem($path) - { - $data = $this->zip->FileRead($path); - if (!$data) { - throw new Exception("Failed to read from EPUB container: $path."); - } - $xml = new DOMDocument(); - $xml->registerNodeClass(DOMElement::class, EpubDomElement::class); - $xml->loadXML($data); - - return new EpubDomXPath($xml); - } - - /** - * Get the stat entries for all files in a ZIP file - * - * @param string $file|null Path to a ZIP file or null for current file - * @return array<mixed> (filename => details of the entry) - */ - public function getZipEntries($file = null) - { - $file ??= $this->file; - $entries = []; - - $zip = new ZipArchive(); - $result = $zip->open($file, ZipArchive::RDONLY); - if ($result !== true) { - throw new Exception("Unable to open file", $result); - } - for ($i = 0; $i < $zip->numFiles; $i++) { - $stat = $zip->statIndex($i); - $entries[$stat['name']] = $stat; - } - $zip->close(); - - return $entries; - } - - /** - * Map the items of a ZIP file to their respective file sizes. - * - * @param string $file|null Path to a ZIP file or null for current ZIP file - * @return array<mixed> (filename => file size) - */ - protected function loadSizeMap($file = null) - { - $entries = $this->getZipEntries($file); - - $sizeMap = []; - foreach ($entries as $filename => $entry) { - $sizeMap[$filename] = $entry['size']; - } - - return $sizeMap; - } - - /** - * @return int - */ - public function getImageCount() - { - $entries = $this->getZipEntries(); - $images = array_filter($entries, static function ($k) { - return preg_match('/(.jpeg|.jpg|.png|.gif)/', $k); - }, ARRAY_FILTER_USE_KEY); - - return count($images); - } -} diff --git a/vendor/mikespub/php-epub-meta/src/Other.php b/vendor/mikespub/php-epub-meta/src/Other.php deleted file mode 100644 index 6d60197ce..000000000 --- a/vendor/mikespub/php-epub-meta/src/Other.php +++ /dev/null @@ -1,121 +0,0 @@ -<?php -/** - * Representation of an EPUB document. - * - * @author Andreas Gohr <andi@splitbrain.org> © 2012 - * @author Simon Schrape <simon@epubli.com> © 2015 - */ - -//namespace Epubli\Epub; - -namespace SebLucas\EPubMeta; - -use SebLucas\EPubMeta\Dom\Element as EpubDomElement; -use SebLucas\EPubMeta\Dom\XPath as EpubDomXPath; -use SebLucas\EPubMeta\Data\Manifest; -use SebLucas\EPubMeta\Contents\Spine; -use SebLucas\EPubMeta\Contents\Toc; -use DOMDocument; -use DOMElement; -use Exception; -use InvalidArgumentException; -use ZipArchive; - -/** - * @todo These are the methods that haven't been integrated with EPub here... - */ -class Other extends EPub -{ - /** - * A simple 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 - * @param string $value New node value - * @param bool|string $attribute Attribute name - * @param bool|string $attributeValue Attribute value - * @param bool $caseSensitive - */ - protected function setMeta($item, $value, $attribute = false, $attributeValue = false, $caseSensitive = true) - { - $xpath = $this->buildMetaXPath($item, $attribute, $attributeValue, $caseSensitive); - - // set value - $nodes = $this->xpath->query($xpath); - if ($nodes->length == 1) { - /** @var EpubDomElement $node */ - $node = $nodes->item(0); - if ($value === '') { - // the user wants to empty this value -> delete the node - $node->delete(); - } else { - // replace value - $node->nodeValueUnescaped = $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 $node) { - /** @var EpubDomElement $node */ - $node->delete(); - } - // re-add them - if ($value) { - $parent = $this->xpath->query('//opf:metadata')->item(0); - $node = new EpubDomElement($item, $value); - $node = $parent->appendChild($node); - if ($attribute) { - if (is_array($attributeValue)) { - // use first given value for new attribute - $attributeValue = reset($attributeValue); - } - $node->setAttrib($attribute, $attributeValue); - } - } - } - - $this->sync(); - } - - /** - * A simple getter for simple meta attributes - * - * It should only be used for attributes that are expected to be unique - * - * @param string $item XML node to get - * @param bool|string $att Attribute name - * @param bool|string $aval Attribute value - * @param bool $caseSensitive - * @return string - */ - protected function getMeta($item, $att = false, $aval = false, $caseSensitive = true) - { - $xpath = $this->buildMetaXPath($item, $att, $aval, $caseSensitive); - - // get value - $nodes = $this->xpath->query($xpath); - if ($nodes->length) { - /** @var EpubDomElement $node */ - $node = $nodes->item(0); - - return $node->nodeValueUnescaped; - } else { - return ''; - } - } - - /** - * Sync XPath object with updated DOM. - */ - protected function sync() - { - $dom = $this->xpath->document; - $dom->loadXML($dom->saveXML()); - $this->xpath = new EpubDomXPath($dom); - // reset structural members - $this->manifest = null; - $this->spine = null; - $this->tocnav = null; - } -} diff --git a/vendor/mikespub/php-epub-meta/src/Tools/HtmlTools.php b/vendor/mikespub/php-epub-meta/src/Tools/HtmlTools.php deleted file mode 100644 index f905d265a..000000000 --- a/vendor/mikespub/php-epub-meta/src/Tools/HtmlTools.php +++ /dev/null @@ -1,97 +0,0 @@ -<?php - -namespace SebLucas\EPubMeta\Tools; - -/** - * From Epubli\Common\Tools - see https://github.com/epubli/common - * @author Epubli Developers <devs@epubli.com> - */ -class HtmlTools -{ - /** - * @param string $html - * @return string - */ - public static function convertEntitiesNamedToNumeric($html) - { - return strtr($html, include(__DIR__ . '/htmlEntityMap.php')); - } - - /** - * @param string $name - * @return bool - */ - public static function isBlockLevelElement($name) - { - return in_array($name, include(__DIR__ . '/htmlBlockLevelElements.php')); - } - - /** - * performs a tag-aware truncation of (html-) strings, preserving tag integrity - * @param array<string>|string $html - * @param int|string $length - * @return bool|string - */ - public static function truncate($html, $length = "20%") - { - $htmls = is_array($html) ? $html : [$html]; - foreach ($htmls as &$htmlString) { - if (is_string($length)) { - $length = trim($length); - /* interpret percentage value */ - if (substr($length, -1) == '%') { - $length = (int) (strlen($htmlString) * intval(substr($length, 0, -1)) / 100); - } - } - $htmlString = substr($htmlString, 0, $length); - /* eliminate trailing truncated tag fragment if present */ - $htmlString = preg_replace('/<[^>]*$/is', '', $htmlString); - } - - return is_array($html) ? $htmls : array_pop($htmls); - } - - /** - * strips all occurring html tags from $html (which can either be a string or an array of strings), - * preserving all content enclosed by all tags in $keep and - * dumping the content residing in all tags listed in $drop - * @param array<string>|string $html - * @param array<string> $keep - * @param array<string> $drop - * @return array<string>|string - */ - public static function stripHtmlTags( - $html, - $keep = - ['title', 'br', 'p', 'h1','h2','h3','h4','h5','span','div','i','strong','b', 'table', 'td', 'th', 'tr'], - $drop = - ['head','style'] - ) { - $htmls = is_array($html) ? $html : [$html]; - foreach ($htmls as &$htmlString) { - foreach ($drop as $dumpTag) { - $htmlString = preg_replace("/<$dumpTag.*$dumpTag>/is", "\n", $htmlString); - } - $htmlString = preg_replace("/[\n\r ]{2,}/i", "\n", $htmlString); - $htmlString = preg_replace("/[\n|\r]/i", '<br />', $htmlString); - - /* @TODO: remove style tags and only keep body content (drop head) */ - $tempFunc = function ($matches) use ($keep) { - $htmlNode = "<" . $matches[1] . ">" . strip_tags($matches[2]) . "</" . $matches[1] . ">"; - if (in_array($matches[1], $keep)) { - return " " . $htmlNode . " "; - } else { - return ""; - } - }; - - $allowedTags = implode("|", array_values($keep)); - $regExp = '@<(' . $allowedTags . ')[^>]*?>(.*?)<\/\1>@i'; - $htmlString = preg_replace_callback($regExp, $tempFunc, $htmlString); - - $htmlString = strip_tags($htmlString, "<" . implode("><", $keep) . ">"); - } - /* preserve injected variable cast type (string|array) when returning processed entity */ - return is_array($html) ? $htmls : array_pop($htmls); - } -} diff --git a/vendor/mikespub/php-epub-meta/src/Tools/ZipEdit.php b/vendor/mikespub/php-epub-meta/src/Tools/ZipEdit.php deleted file mode 100644 index ff9f607cf..000000000 --- a/vendor/mikespub/php-epub-meta/src/Tools/ZipEdit.php +++ /dev/null @@ -1,450 +0,0 @@ -<?php -/** - * ZipEdit class - * - * @author mikespub - */ - -namespace SebLucas\EPubMeta\Tools; - -use ZipStream\ZipStream; -use DateTime; -use Exception; -use ZipArchive; - -/** - * ZipEdit class allows to edit zip files on the fly and stream them afterwards - */ -class ZipEdit -{ - public const DOWNLOAD = 1; // download (default) - public const NOHEADER = 4; // option to use with DOWNLOAD: no header is sent - public const FILE = 8; // output to file , or add from file - public const STRING = 32; // output to string, or add from string - public const MIME_TYPE = 'application/epub+zip'; - - /** @var ZipArchive|null */ - private $mZip; - /** @var array<string, mixed>|null */ - private $mEntries; - /** @var array<string, mixed> */ - private $mChanges = []; - /** @var string|null */ - private $mFileName; - private bool $mSaveMe = false; - - public function __construct() - { - $this->mZip = null; - $this->mEntries = null; - $this->mFileName = null; - } - - /** - * Destructor - */ - public function __destruct() - { - $this->Close(); - } - - /** - * Open a zip file and read it's entries - * - * @param string $inFileName - * @param int|null $inFlags - * @return boolean True if zip file has been correctly opended, else false - */ - public function Open($inFileName, $inFlags = 0) // ZipArchive::RDONLY) - { - $this->Close(); - - $this->mZip = new ZipArchive(); - $result = $this->mZip->open($inFileName, ZipArchive::RDONLY); - if ($result !== true) { - return false; - } - - $this->mFileName = $inFileName; - - $this->mEntries = []; - $this->mChanges = []; - - for ($i = 0; $i < $this->mZip->numFiles; $i++) { - $entry = $this->mZip->statIndex($i); - $fileName = $entry['name']; - $this->mEntries[$fileName] = $entry; - $this->mChanges[$fileName] = ['status' => 'unchanged']; - } - - return true; - } - - /** - * Check if a file exist in the zip entries - * - * @param string $inFileName File to search - * - * @return boolean True if the file exist, else false - */ - public function FileExists($inFileName) - { - if (!isset($this->mZip)) { - return false; - } - - if (!isset($this->mEntries[$inFileName])) { - return false; - } - - return true; - } - - /** - * Read the content of a file in the zip entries - * - * @param string $inFileName File to search - * - * @return mixed File content the file exist, else false - */ - public function FileRead($inFileName) - { - if (!isset($this->mZip)) { - return false; - } - - if (!isset($this->mEntries[$inFileName])) { - return false; - } - - $data = false; - - $changes = $this->mChanges[$inFileName] ?? ['status' => 'unchanged']; - switch ($changes['status']) { - case 'unchanged': - $data = $this->mZip->getFromName($inFileName); - break; - case 'added': - case 'modified': - if (isset($changes['data'])) { - $data = $changes['data']; - } elseif (isset($changes['path'])) { - $data = file_get_contents($changes['path']); - } - break; - case 'deleted': - default: - break; - } - return $data; - } - - /** - * Get a file handler to a file in the zip entries (read-only) - * - * @param string $inFileName File to search - * - * @return resource|bool File handler if the file exist, else false - */ - public function FileStream($inFileName) - { - if (!isset($this->mZip)) { - return false; - } - - if (!isset($this->mEntries[$inFileName])) { - return false; - } - - // @todo streaming of added/modified data? - return $this->mZip->getStream($inFileName); - } - - /** - * Summary of FileAdd - * @param string $inFileName - * @param mixed $inData - * @return bool - */ - public function FileAdd($inFileName, $inData) - { - if (!isset($this->mZip)) { - return false; - } - - $this->mEntries[$inFileName] = [ - 'name' => $inFileName, // 'foobar/baz', - 'size' => strlen($inData), - 'mtime' => time(), // 1123164748, - ]; - $this->mChanges[$inFileName] = ['status' => 'added', 'data' => $inData]; - return true; - } - - /** - * Summary of FileAddPath - * @param string $inFileName - * @param string $inFilePath - * @return bool - */ - public function FileAddPath($inFileName, $inFilePath) - { - if (!isset($this->mZip)) { - return false; - } - - $this->mEntries[$inFileName] = [ - 'name' => $inFileName, // 'foobar/baz', - 'size' => filesize($inFilePath), - 'mtime' => filemtime($inFilePath), // 1123164748, - ]; - $this->mChanges[$inFileName] = ['status' => 'added', 'path' => $inFilePath]; - return true; - } - - /** - * Summary of FileDelete - * @param string $inFileName - * @return bool - */ - public function FileDelete($inFileName) - { - if (!$this->FileExists($inFileName)) { - return false; - } - - $this->mEntries[$inFileName]['size'] = 0; - $this->mEntries[$inFileName]['mtime'] = time(); - $this->mChanges[$inFileName] = ['status' => 'deleted']; - return true; - } - - /** - * Replace the content of a file in the zip entries - * - * @param string $inFileName File with content to replace - * @param string|bool $inData Data content to replace, or false to delete - * @return bool - */ - public function FileReplace($inFileName, $inData) - { - if (!isset($this->mZip)) { - return false; - } - - if ($inData === false) { - return $this->FileDelete($inFileName); - } - - $this->mEntries[$inFileName] ??= []; - $this->mEntries[$inFileName]['name'] = $inFileName; - $this->mEntries[$inFileName]['size'] = strlen($inData); - $this->mEntries[$inFileName]['mtime'] = time(); - $this->mChanges[$inFileName] = ['status' => 'modified', 'data' => $inData]; - return true; - } - - /** - * Return the state of the file. - * @param mixed $inFileName - * @return string|bool 'u'=unchanged, 'm'=modified, 'd'=deleted, 'a'=added, false=unknown - */ - public function FileGetState($inFileName) - { - $changes = $this->mChanges[$inFileName] ?? ['status' => false]; - return $changes['status']; - } - - /** - * Summary of FileCancelModif - * @param string $inFileName - * @param bool $ReplacedAndDeleted - * @return int - */ - public function FileCancelModif($inFileName, $ReplacedAndDeleted = true) - { - // cancel added, modified or deleted modifications on a file in the archive - // return the number of cancels - - $nbr = 0; - - $this->mChanges[$inFileName] = ['status' => 'unchanged']; - $nbr += 1; - return $nbr; - } - - /** - * Close the zip file - * - * @return void - * @throws Exception - */ - public function Close() - { - if (!isset($this->mZip)) { - return; - } - - $outFileName = $this->mFileName . '.copy'; - if ($this->mSaveMe) { - $outFileStream = fopen($outFileName, 'wb+'); - if ($outFileStream === false) { - throw new Exception('Unable to open zip copy ' . $outFileName); - } - $this->Flush(self::DOWNLOAD, $this->mFileName, self::MIME_TYPE, false, $outFileStream); - $result = fclose($outFileStream); - if ($result === false) { - throw new Exception('Unable to close zip copy ' . $outFileName); - } - } - - if (!$this->mZip->close()) { - $status = $this->mZip->getStatusString(); - $this->mZip = null; - throw new Exception($status); - } - if ($this->mSaveMe) { - $result = rename($outFileName, $this->mFileName); - if ($result === false) { - throw new Exception('Unable to rename zip copy ' . $outFileName); - } - $this->mSaveMe = false; - } - $this->mZip = null; - } - - /** - * Summary of SaveBeforeClose - * @return void - */ - public function SaveBeforeClose() - { - // Coming from EPub()->download() without fileName, called in EPub()->save() - // This comes right before EPub()->zip->close(), at which point we're lost - $this->mSaveMe = true; - } - - /** - * Summary of Flush - * @param mixed $render - * @param mixed $outFileName - * @param mixed $contentType - * @param bool $sendHeaders - * @param resource|null $outFileStream - * @return void - */ - public function Flush($render = self::DOWNLOAD, $outFileName = '', $contentType = '', $sendHeaders = true, $outFileStream = null) - { - // we don't want to close the zip file to save all changes here - probably what you needed :-) - //$this->Close(); - - $outFileName = $outFileName ?: $this->mFileName; - $contentType = $contentType ?: self::MIME_TYPE; - if ($outFileStream) { - $sendHeaders = false; - } - if (!$sendHeaders) { - $render = $render | self::NOHEADER; - } - if (($render & self::NOHEADER) !== self::NOHEADER) { - $sendHeaders = true; - } else { - $sendHeaders = false; - } - - $outZipStream = new ZipStream( - outputName: basename($outFileName), - outputStream: $outFileStream, - sendHttpHeaders: $sendHeaders, - contentType: $contentType, - ); - foreach ($this->mEntries as $fileName => $entry) { - $changes = $this->mChanges[$fileName]; - switch ($changes['status']) { - case 'unchanged': - // Automatic binding of $this - $callback = function () use ($fileName) { - // this expects a stream as result, not the actual data - return $this->mZip->getStream($fileName); - }; - $date = new DateTime(); - $date->setTimestamp($entry['mtime']); - $outZipStream->addFileFromCallback( - fileName: $fileName, - exactSize: $entry['size'], - lastModificationDateTime: $date, - callback: $callback, - ); - break; - case 'added': - case 'modified': - if (isset($changes['data'])) { - $outZipStream->addFile( - fileName: $fileName, - data: $changes['data'], - ); - } elseif (isset($changes['path'])) { - $outZipStream->addFileFromPath( - fileName: $fileName, - path: $changes['path'], - ); - } - break; - case 'deleted': - default: - break; - } - } - - $outZipStream->finish(); - } - - /** - * Summary of copyTest - * @param string $inFileName - * @param string $outFileName - * @return void - */ - public static function copyTest($inFileName, $outFileName) - { - $inZipFile = new ZipArchive(); - $result = $inZipFile->open($inFileName, ZipArchive::RDONLY); - if ($result !== true) { - throw new Exception('Unable to open zip file ' . $inFileName); - } - - $entries = []; - for ($i = 0; $i < $inZipFile->numFiles; $i++) { - $entry = $inZipFile->statIndex($i); - $fileName = $entry['name']; - $entries[$fileName] = $entry; - } - - // see ZipStreamTest.php - $outFileStream = fopen($outFileName, 'wb+'); - - $outZipStream = new ZipStream( - outputName: basename($outFileName), - outputStream: $outFileStream, - sendHttpHeaders: false, - ); - foreach ($entries as $fileName => $entry) { - $date = new DateTime(); - $date->setTimestamp($entry['mtime']); - // does not work in v2 - the zip stream is not seekable, but ZipStream checks for it in Stream.php - // does work in v3 - implemented using addFileFromCallback, so we might as well use that :-) - $outZipStream->addFileFromCallback( - fileName: $fileName, - exactSize: $entry['size'], - lastModificationDateTime: $date, - callback: function () use ($inZipFile, $fileName) { - // this expects a stream as result, not the actual data - return $inZipFile->getStream($fileName); - }, - ); - } - - $outZipStream->finish(); - fclose($outFileStream); - } -} diff --git a/vendor/mikespub/php-epub-meta/src/Tools/ZipFile.php b/vendor/mikespub/php-epub-meta/src/Tools/ZipFile.php deleted file mode 100644 index b083b0c4f..000000000 --- a/vendor/mikespub/php-epub-meta/src/Tools/ZipFile.php +++ /dev/null @@ -1,343 +0,0 @@ -<?php -/** - * ZipFile class - * - * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) - * @author Didier Corbière <contact@atoll-digital-library.org> - * @author mikespub - */ - -namespace SebLucas\EPubMeta\Tools; - -use Exception; -use ZipArchive; - -/** - * ZipFile class allows to open files inside a zip file with the standard php zip functions - * - * This class also supports adding/replacing/deleting files inside the zip file, but changes - * will *not* be reflected correctly until you close the zip file, and open it again if needed - * - * Note: this is not meant to handle a massive amount of files inside generic archive files. - * It is specifically meant for EPUB files which typically contain hundreds/thousands of pages, - * not millions or more. And any changes you make are kept in memory, so don't re-write every - * page of War and Peace either - better to unzip that locally and then re-zip it afterwards. - */ -class ZipFile -{ - public const DOWNLOAD = 1; // download (default) - public const NOHEADER = 4; // option to use with DOWNLOAD: no header is sent - public const FILE = 8; // output to file , or add from file - public const STRING = 32; // output to string, or add from string - public const MIME_TYPE = 'application/epub+zip'; - - /** @var ZipArchive|null */ - protected $mZip; - /** @var array<string, mixed>|null */ - protected $mEntries; - /** @var array<string, mixed> */ - protected $mChanges = []; - /** @var string|null */ - protected $mFileName; - - public function __construct() - { - $this->mZip = null; - $this->mEntries = null; - $this->mFileName = null; - } - - /** - * Destructor - */ - public function __destruct() - { - $this->Close(); - } - - /** - * Open a zip file and read it's entries - * - * @param string $inFileName - * @param int|null $inFlags - * @return boolean True if zip file has been correctly opended, else false - */ - public function Open($inFileName, $inFlags = 0) // ZipArchive::RDONLY) - { - $this->Close(); - $inFileName = realpath($inFileName); - - $this->mZip = new ZipArchive(); - $result = $this->mZip->open($inFileName, $inFlags); - if ($result !== true) { - return false; - } - - $this->mFileName = $inFileName; - - $this->mEntries = []; - $this->mChanges = []; - - for ($i = 0; $i < $this->mZip->numFiles; $i++) { - $entry = $this->mZip->statIndex($i); - $fileName = $entry['name']; - $this->mEntries[$fileName] = $entry; - $this->mChanges[$fileName] = ['status' => 'unchanged']; - } - - return true; - } - - /** - * Check if a file exist in the zip entries - * - * @param string $inFileName File to search - * - * @return boolean True if the file exist, else false - */ - public function FileExists($inFileName) - { - if (!isset($this->mZip)) { - return false; - } - - if (!isset($this->mEntries[$inFileName])) { - return false; - } - - return true; - } - - /** - * Read the content of a file in the zip entries - * - * @param string $inFileName File to search - * - * @return mixed File content the file exist, else false - */ - public function FileRead($inFileName) - { - if (!isset($this->mZip)) { - return false; - } - - if (!isset($this->mEntries[$inFileName])) { - return false; - } - - $data = false; - - $changes = $this->mChanges[$inFileName] ?? ['status' => 'unchanged']; - switch ($changes['status']) { - case 'unchanged': - $data = $this->mZip->getFromName($inFileName); - break; - case 'added': - case 'modified': - if (isset($changes['data'])) { - $data = $changes['data']; - } elseif (isset($changes['path'])) { - $data = file_get_contents($changes['path']); - } - break; - case 'deleted': - default: - break; - } - return $data; - } - - /** - * Get a file handler to a file in the zip entries (read-only) - * - * @param string $inFileName File to search - * - * @return resource|bool File handler if the file exist, else false - */ - public function FileStream($inFileName) - { - if (!isset($this->mZip)) { - return false; - } - - if (!isset($this->mEntries[$inFileName])) { - return false; - } - - // @todo streaming of added/modified data? - return $this->mZip->getStream($inFileName); - } - - /** - * Summary of FileAdd - * @param string $inFileName - * @param mixed $inData - * @return bool - */ - public function FileAdd($inFileName, $inData) - { - if (!isset($this->mZip)) { - return false; - } - - if (!$this->mZip->addFromString($inFileName, $inData)) { - return false; - } - $this->mEntries[$inFileName] = $this->mZip->statName($inFileName); - $this->mChanges[$inFileName] = ['status' => 'added', 'data' => $inData]; - return true; - } - - /** - * Summary of FileAddPath - * @param string $inFileName - * @param string $inFilePath - * @return mixed - */ - public function FileAddPath($inFileName, $inFilePath) - { - if (!isset($this->mZip)) { - return false; - } - - if (!$this->mZip->addFile($inFilePath, $inFileName)) { - return false; - } - $this->mEntries[$inFileName] = $this->mZip->statName($inFileName); - $this->mChanges[$inFileName] = ['status' => 'added', 'path' => $inFilePath]; - return true; - } - - /** - * Summary of FileDelete - * @param string $inFileName - * @return bool - */ - public function FileDelete($inFileName) - { - if (!$this->FileExists($inFileName)) { - return false; - } - - if (!$this->mZip->deleteName($inFileName)) { - return false; - } - unset($this->mEntries[$inFileName]); - $this->mChanges[$inFileName] = ['status' => 'deleted']; - return true; - } - - /** - * Replace the content of a file in the zip entries - * - * @param string $inFileName File with content to replace - * @param string|bool $inData Data content to replace, or false to delete - * @return bool - */ - public function FileReplace($inFileName, $inData) - { - if (!isset($this->mZip)) { - return false; - } - - if ($inData === false) { - return $this->FileDelete($inFileName); - } - - if (!$this->mZip->addFromString($inFileName, $inData)) { - return false; - } - $this->mEntries[$inFileName] = $this->mZip->statName($inFileName); - $this->mChanges[$inFileName] = ['status' => 'modified', 'data' => $inData]; - return true; - } - - /** - * Return the state of the file. - * @param mixed $inFileName - * @return string|bool 'u'=unchanged, 'm'=modified, 'd'=deleted, 'a'=added, false=unknown - */ - public function FileGetState($inFileName) - { - $changes = $this->mChanges[$inFileName] ?? ['status' => false]; - return $changes['status']; - } - - /** - * Summary of FileCancelModif - * @param string $inFileName - * @param bool $ReplacedAndDeleted - * @return int - */ - public function FileCancelModif($inFileName, $ReplacedAndDeleted = true) - { - // cancel added, modified or deleted modifications on a file in the archive - // return the number of cancels - - $nbr = 0; - - if (!$this->mZip->unchangeName($inFileName)) { - return $nbr; - } - $nbr += 1; - $this->mChanges[$inFileName] = ['status' => 'unchanged']; - return $nbr; - } - - /** - * Close the zip file - * - * @return void - * @throws Exception - */ - public function Close() - { - if (!isset($this->mZip)) { - return; - } - - if (!$this->mZip->close()) { - $status = $this->mZip->getStatusString(); - $this->mZip = null; - throw new Exception($status); - } - $this->mZip = null; - } - - /** - * Summary of Flush - * @param mixed $render - * @param mixed $outFileName - * @param mixed $contentType - * @param bool $sendHeaders - * @return void - */ - public function Flush($render = self::DOWNLOAD, $outFileName = '', $contentType = '', $sendHeaders = true) - { - // we need to close the zip file to save all changes here - probably not what you wanted :-() - $this->Close(); - - $outFileName = $outFileName ?: $this->mFileName; - $contentType = $contentType ?: static::MIME_TYPE; - if (!$sendHeaders) { - $render = $render | static::NOHEADER; - } - $inFilePath = realpath($this->mFileName); - - if (($render & static::NOHEADER) !== static::NOHEADER) { - $expires = 60 * 60 * 24 * 14; - header('Pragma: public'); - header('Cache-Control: max-age=' . $expires); - header('Expires: ' . gmdate('D, d M Y H:i:s', time() + $expires) . ' GMT'); - - header('Content-Type: ' . $contentType); - header('Content-Disposition: attachment; filename="' . basename($outFileName) . '"'); - - // see fetch.php for use of Config::get('x_accel_redirect') - header('Content-Length: ' . filesize($inFilePath)); - //header(Config::get('x_accel_redirect') . ': ' . $inFilePath); - } - - readfile($inFilePath); - } -} diff --git a/vendor/mikespub/php-epub-meta/src/Tools/htmlBlockLevelElements.php b/vendor/mikespub/php-epub-meta/src/Tools/htmlBlockLevelElements.php deleted file mode 100644 index 6e96a6b0f..000000000 --- a/vendor/mikespub/php-epub-meta/src/Tools/htmlBlockLevelElements.php +++ /dev/null @@ -1,42 +0,0 @@ -<?php -/** - * List of HTML block level elements. - * Source: https://developer.mozilla.org/en-US/docs/Web/HTML/Block-level_elements - */ -return [ - 'address', - 'article', - 'aside', - 'audio', - 'video', - 'blockquote', - 'canvas', - 'dd', - 'div', - 'dl', - 'fieldset', - 'figcaption', - 'figure', - 'footer', - 'form', - 'h1', - 'h2', - 'h3', - 'h4', - 'h5', - 'h6', - 'header', - 'hgroup', - 'hr', - 'li', - 'noscript', - 'ol', - 'output', - 'p', - 'pre', - 'section', - 'table', - 'tfoot', - 'ul', - 'video', -]; diff --git a/vendor/mikespub/php-epub-meta/src/Tools/htmlEntityMap.php b/vendor/mikespub/php-epub-meta/src/Tools/htmlEntityMap.php deleted file mode 100644 index 6bc3f4653..000000000 --- a/vendor/mikespub/php-epub-meta/src/Tools/htmlEntityMap.php +++ /dev/null @@ -1,260 +0,0 @@ -<?php -/** - * Maps every HTML named entity to its numeric equivalent. - * Source: http://stackoverflow.com/questions/11176752/converting-named-html-entities-to-numeric-html-entities#answer-11179875 - */ -return [ -' ' => ' ', # no-break space = non-breaking space, U+00A0 ISOnum -'¡' => '¡', # inverted exclamation mark, U+00A1 ISOnum -'¢' => '¢', # cent sign, U+00A2 ISOnum -'£' => '£', # pound sign, U+00A3 ISOnum -'¤' => '¤', # currency sign, U+00A4 ISOnum -'¥' => '¥', # yen sign = yuan sign, U+00A5 ISOnum -'¦' => '¦', # broken bar = broken vertical bar, U+00A6 ISOnum -'§' => '§', # section sign, U+00A7 ISOnum -'¨' => '¨', # diaeresis = spacing diaeresis, U+00A8 ISOdia -'©' => '©', # copyright sign, U+00A9 ISOnum -'ª' => 'ª', # feminine ordinal indicator, U+00AA ISOnum -'«' => '«', # left-pointing double angle quotation mark = left pointing guillemet, U+00AB ISOnum -'¬' => '¬', # not sign, U+00AC ISOnum -'­' => '­', # soft hyphen = discretionary hyphen, U+00AD ISOnum -'®' => '®', # registered sign = registered trade mark sign, U+00AE ISOnum -'¯' => '¯', # macron = spacing macron = overline = APL overbar, U+00AF ISOdia -'°' => '°', # degree sign, U+00B0 ISOnum -'±' => '±', # plus-minus sign = plus-or-minus sign, U+00B1 ISOnum -'²' => '²', # superscript two = superscript digit two = squared, U+00B2 ISOnum -'³' => '³', # superscript three = superscript digit three = cubed, U+00B3 ISOnum -'´' => '´', # acute accent = spacing acute, U+00B4 ISOdia -'µ' => 'µ', # micro sign, U+00B5 ISOnum -'¶' => '¶', # pilcrow sign = paragraph sign, U+00B6 ISOnum -'·' => '·', # middle dot = Georgian comma = Greek middle dot, U+00B7 ISOnum -'¸' => '¸', # cedilla = spacing cedilla, U+00B8 ISOdia -'¹' => '¹', # superscript one = superscript digit one, U+00B9 ISOnum -'º' => 'º', # masculine ordinal indicator, U+00BA ISOnum -'»' => '»', # right-pointing double angle quotation mark = right pointing guillemet, U+00BB ISOnum -'¼' => '¼', # vulgar fraction one quarter = fraction one quarter, U+00BC ISOnum -'½' => '½', # vulgar fraction one half = fraction one half, U+00BD ISOnum -'¾' => '¾', # vulgar fraction three quarters = fraction three quarters, U+00BE ISOnum -'¿' => '¿', # inverted question mark = turned question mark, U+00BF ISOnum -'À' => 'À', # latin capital letter A with grave = latin capital letter A grave, U+00C0 ISOlat1 -'Á' => 'Á', # latin capital letter A with acute, U+00C1 ISOlat1 -'Â' => 'Â', # latin capital letter A with circumflex, U+00C2 ISOlat1 -'Ã' => 'Ã', # latin capital letter A with tilde, U+00C3 ISOlat1 -'Ä' => 'Ä', # latin capital letter A with diaeresis, U+00C4 ISOlat1 -'Å' => 'Å', # latin capital letter A with ring above = latin capital letter A ring, U+00C5 ISOlat1 -'Æ' => 'Æ', # latin capital letter AE = latin capital ligature AE, U+00C6 ISOlat1 -'Ç' => 'Ç', # latin capital letter C with cedilla, U+00C7 ISOlat1 -'È' => 'È', # latin capital letter E with grave, U+00C8 ISOlat1 -'É' => 'É', # latin capital letter E with acute, U+00C9 ISOlat1 -'Ê' => 'Ê', # latin capital letter E with circumflex, U+00CA ISOlat1 -'Ë' => 'Ë', # latin capital letter E with diaeresis, U+00CB ISOlat1 -'Ì' => 'Ì', # latin capital letter I with grave, U+00CC ISOlat1 -'Í' => 'Í', # latin capital letter I with acute, U+00CD ISOlat1 -'Î' => 'Î', # latin capital letter I with circumflex, U+00CE ISOlat1 -'Ï' => 'Ï', # latin capital letter I with diaeresis, U+00CF ISOlat1 -'Ð' => 'Ð', # latin capital letter ETH, U+00D0 ISOlat1 -'Ñ' => 'Ñ', # latin capital letter N with tilde, U+00D1 ISOlat1 -'Ò' => 'Ò', # latin capital letter O with grave, U+00D2 ISOlat1 -'Ó' => 'Ó', # latin capital letter O with acute, U+00D3 ISOlat1 -'Ô' => 'Ô', # latin capital letter O with circumflex, U+00D4 ISOlat1 -'Õ' => 'Õ', # latin capital letter O with tilde, U+00D5 ISOlat1 -'Ö' => 'Ö', # latin capital letter O with diaeresis, U+00D6 ISOlat1 -'×' => '×', # multiplication sign, U+00D7 ISOnum -'Ø' => 'Ø', # latin capital letter O with stroke = latin capital letter O slash, U+00D8 ISOlat1 -'Ù' => 'Ù', # latin capital letter U with grave, U+00D9 ISOlat1 -'Ú' => 'Ú', # latin capital letter U with acute, U+00DA ISOlat1 -'Û' => 'Û', # latin capital letter U with circumflex, U+00DB ISOlat1 -'Ü' => 'Ü', # latin capital letter U with diaeresis, U+00DC ISOlat1 -'Ý' => 'Ý', # latin capital letter Y with acute, U+00DD ISOlat1 -'Þ' => 'Þ', # latin capital letter THORN, U+00DE ISOlat1 -'ß' => 'ß', # latin small letter sharp s = ess-zed, U+00DF ISOlat1 -'à' => 'à', # latin small letter a with grave = latin small letter a grave, U+00E0 ISOlat1 -'á' => 'á', # latin small letter a with acute, U+00E1 ISOlat1 -'â' => 'â', # latin small letter a with circumflex, U+00E2 ISOlat1 -'ã' => 'ã', # latin small letter a with tilde, U+00E3 ISOlat1 -'ä' => 'ä', # latin small letter a with diaeresis, U+00E4 ISOlat1 -'å' => 'å', # latin small letter a with ring above = latin small letter a ring, U+00E5 ISOlat1 -'æ' => 'æ', # latin small letter ae = latin small ligature ae, U+00E6 ISOlat1 -'ç' => 'ç', # latin small letter c with cedilla, U+00E7 ISOlat1 -'è' => 'è', # latin small letter e with grave, U+00E8 ISOlat1 -'é' => 'é', # latin small letter e with acute, U+00E9 ISOlat1 -'ê' => 'ê', # latin small letter e with circumflex, U+00EA ISOlat1 -'ë' => 'ë', # latin small letter e with diaeresis, U+00EB ISOlat1 -'ì' => 'ì', # latin small letter i with grave, U+00EC ISOlat1 -'í' => 'í', # latin small letter i with acute, U+00ED ISOlat1 -'î' => 'î', # latin small letter i with circumflex, U+00EE ISOlat1 -'ï' => 'ï', # latin small letter i with diaeresis, U+00EF ISOlat1 -'ð' => 'ð', # latin small letter eth, U+00F0 ISOlat1 -'ñ' => 'ñ', # latin small letter n with tilde, U+00F1 ISOlat1 -'ò' => 'ò', # latin small letter o with grave, U+00F2 ISOlat1 -'ó' => 'ó', # latin small letter o with acute, U+00F3 ISOlat1 -'ô' => 'ô', # latin small letter o with circumflex, U+00F4 ISOlat1 -'õ' => 'õ', # latin small letter o with tilde, U+00F5 ISOlat1 -'ö' => 'ö', # latin small letter o with diaeresis, U+00F6 ISOlat1 -'÷' => '÷', # division sign, U+00F7 ISOnum -'ø' => 'ø', # latin small letter o with stroke, = latin small letter o slash, U+00F8 ISOlat1 -'ù' => 'ù', # latin small letter u with grave, U+00F9 ISOlat1 -'ú' => 'ú', # latin small letter u with acute, U+00FA ISOlat1 -'û' => 'û', # latin small letter u with circumflex, U+00FB ISOlat1 -'ü' => 'ü', # latin small letter u with diaeresis, U+00FC ISOlat1 -'ý' => 'ý', # latin small letter y with acute, U+00FD ISOlat1 -'þ' => 'þ', # latin small letter thorn, U+00FE ISOlat1 -'ÿ' => 'ÿ', # latin small letter y with diaeresis, U+00FF ISOlat1 -'ƒ' => 'ƒ', # latin small f with hook = function = florin, U+0192 ISOtech -'Α' => 'Α', # greek capital letter alpha, U+0391 -'Β' => 'Β', # greek capital letter beta, U+0392 -'Γ' => 'Γ', # greek capital letter gamma, U+0393 ISOgrk3 -'Δ' => 'Δ', # greek capital letter delta, U+0394 ISOgrk3 -'Ε' => 'Ε', # greek capital letter epsilon, U+0395 -'Ζ' => 'Ζ', # greek capital letter zeta, U+0396 -'Η' => 'Η', # greek capital letter eta, U+0397 -'Θ' => 'Θ', # greek capital letter theta, U+0398 ISOgrk3 -'Ι' => 'Ι', # greek capital letter iota, U+0399 -'Κ' => 'Κ', # greek capital letter kappa, U+039A -'Λ' => 'Λ', # greek capital letter lambda, U+039B ISOgrk3 -'Μ' => 'Μ', # greek capital letter mu, U+039C -'Ν' => 'Ν', # greek capital letter nu, U+039D -'Ξ' => 'Ξ', # greek capital letter xi, U+039E ISOgrk3 -'Ο' => 'Ο', # greek capital letter omicron, U+039F -'Π' => 'Π', # greek capital letter pi, U+03A0 ISOgrk3 -'Ρ' => 'Ρ', # greek capital letter rho, U+03A1 -'Σ' => 'Σ', # greek capital letter sigma, U+03A3 ISOgrk3 -'Τ' => 'Τ', # greek capital letter tau, U+03A4 -'Υ' => 'Υ', # greek capital letter upsilon, U+03A5 ISOgrk3 -'Φ' => 'Φ', # greek capital letter phi, U+03A6 ISOgrk3 -'Χ' => 'Χ', # greek capital letter chi, U+03A7 -'Ψ' => 'Ψ', # greek capital letter psi, U+03A8 ISOgrk3 -'Ω' => 'Ω', # greek capital letter omega, U+03A9 ISOgrk3 -'α' => 'α', # greek small letter alpha, U+03B1 ISOgrk3 -'β' => 'β', # greek small letter beta, U+03B2 ISOgrk3 -'γ' => 'γ', # greek small letter gamma, U+03B3 ISOgrk3 -'δ' => 'δ', # greek small letter delta, U+03B4 ISOgrk3 -'ε' => 'ε', # greek small letter epsilon, U+03B5 ISOgrk3 -'ζ' => 'ζ', # greek small letter zeta, U+03B6 ISOgrk3 -'η' => 'η', # greek small letter eta, U+03B7 ISOgrk3 -'θ' => 'θ', # greek small letter theta, U+03B8 ISOgrk3 -'ι' => 'ι', # greek small letter iota, U+03B9 ISOgrk3 -'κ' => 'κ', # greek small letter kappa, U+03BA ISOgrk3 -'λ' => 'λ', # greek small letter lambda, U+03BB ISOgrk3 -'μ' => 'μ', # greek small letter mu, U+03BC ISOgrk3 -'ν' => 'ν', # greek small letter nu, U+03BD ISOgrk3 -'ξ' => 'ξ', # greek small letter xi, U+03BE ISOgrk3 -'ο' => 'ο', # greek small letter omicron, U+03BF NEW -'π' => 'π', # greek small letter pi, U+03C0 ISOgrk3 -'ρ' => 'ρ', # greek small letter rho, U+03C1 ISOgrk3 -'ς' => 'ς', # greek small letter final sigma, U+03C2 ISOgrk3 -'σ' => 'σ', # greek small letter sigma, U+03C3 ISOgrk3 -'τ' => 'τ', # greek small letter tau, U+03C4 ISOgrk3 -'υ' => 'υ', # greek small letter upsilon, U+03C5 ISOgrk3 -'φ' => 'φ', # greek small letter phi, U+03C6 ISOgrk3 -'χ' => 'χ', # greek small letter chi, U+03C7 ISOgrk3 -'ψ' => 'ψ', # greek small letter psi, U+03C8 ISOgrk3 -'ω' => 'ω', # greek small letter omega, U+03C9 ISOgrk3 -'ϑ' => 'ϑ', # greek small letter theta symbol, U+03D1 NEW -'ϒ' => 'ϒ', # greek upsilon with hook symbol, U+03D2 NEW -'ϖ' => 'ϖ', # greek pi symbol, U+03D6 ISOgrk3 -'•' => '•', # bullet = black small circle, U+2022 ISOpub -'…' => '…', # horizontal ellipsis = three dot leader, U+2026 ISOpub -'′' => '′', # prime = minutes = feet, U+2032 ISOtech -'″' => '″', # double prime = seconds = inches, U+2033 ISOtech -'‾' => '‾', # overline = spacing overscore, U+203E NEW -'⁄' => '⁄', # fraction slash, U+2044 NEW -'℘' => '℘', # script capital P = power set = Weierstrass p, U+2118 ISOamso -'ℑ' => 'ℑ', # blackletter capital I = imaginary part, U+2111 ISOamso -'ℜ' => 'ℜ', # blackletter capital R = real part symbol, U+211C ISOamso -'™' => '™', # trade mark sign, U+2122 ISOnum -'ℵ' => 'ℵ', # alef symbol = first transfinite cardinal, U+2135 NEW -'←' => '←', # leftwards arrow, U+2190 ISOnum -'↑' => '↑', # upwards arrow, U+2191 ISOnum -'→' => '→', # rightwards arrow, U+2192 ISOnum -'↓' => '↓', # downwards arrow, U+2193 ISOnum -'↔' => '↔', # left right arrow, U+2194 ISOamsa -'↵' => '↵', # downwards arrow with corner leftwards = carriage return, U+21B5 NEW -'⇐' => '⇐', # leftwards double arrow, U+21D0 ISOtech -'⇑' => '⇑', # upwards double arrow, U+21D1 ISOamsa -'⇒' => '⇒', # rightwards double arrow, U+21D2 ISOtech -'⇓' => '⇓', # downwards double arrow, U+21D3 ISOamsa -'⇔' => '⇔', # left right double arrow, U+21D4 ISOamsa -'∀' => '∀', # for all, U+2200 ISOtech -'∂' => '∂', # partial differential, U+2202 ISOtech -'∃' => '∃', # there exists, U+2203 ISOtech -'∅' => '∅', # empty set = null set = diameter, U+2205 ISOamso -'∇' => '∇', # nabla = backward difference, U+2207 ISOtech -'∈' => '∈', # element of, U+2208 ISOtech -'∉' => '∉', # not an element of, U+2209 ISOtech -'∋' => '∋', # contains as member, U+220B ISOtech -'∏' => '∏', # n-ary product = product sign, U+220F ISOamsb -'∑' => '∑', # n-ary sumation, U+2211 ISOamsb -'−' => '−', # minus sign, U+2212 ISOtech -'∗' => '∗', # asterisk operator, U+2217 ISOtech -'√' => '√', # square root = radical sign, U+221A ISOtech -'∝' => '∝', # proportional to, U+221D ISOtech -'∞' => '∞', # infinity, U+221E ISOtech -'∠' => '∠', # angle, U+2220 ISOamso -'∧' => '∧', # logical and = wedge, U+2227 ISOtech -'∨' => '∨', # logical or = vee, U+2228 ISOtech -'∩' => '∩', # intersection = cap, U+2229 ISOtech -'∪' => '∪', # union = cup, U+222A ISOtech -'∫' => '∫', # integral, U+222B ISOtech -'∴' => '∴', # therefore, U+2234 ISOtech -'∼' => '∼', # tilde operator = varies with = similar to, U+223C ISOtech -'≅' => '≅', # approximately equal to, U+2245 ISOtech -'≈' => '≈', # almost equal to = asymptotic to, U+2248 ISOamsr -'≠' => '≠', # not equal to, U+2260 ISOtech -'≡' => '≡', # identical to, U+2261 ISOtech -'≤' => '≤', # less-than or equal to, U+2264 ISOtech -'≥' => '≥', # greater-than or equal to, U+2265 ISOtech -'⊂' => '⊂', # subset of, U+2282 ISOtech -'⊃' => '⊃', # superset of, U+2283 ISOtech -'⊄' => '⊄', # not a subset of, U+2284 ISOamsn -'⊆' => '⊆', # subset of or equal to, U+2286 ISOtech -'⊇' => '⊇', # superset of or equal to, U+2287 ISOtech -'⊕' => '⊕', # circled plus = direct sum, U+2295 ISOamsb -'⊗' => '⊗', # circled times = vector product, U+2297 ISOamsb -'⊥' => '⊥', # up tack = orthogonal to = perpendicular, U+22A5 ISOtech -'⋅' => '⋅', # dot operator, U+22C5 ISOamsb -'⌈' => '⌈', # left ceiling = apl upstile, U+2308 ISOamsc -'⌉' => '⌉', # right ceiling, U+2309 ISOamsc -'⌊' => '⌊', # left floor = apl downstile, U+230A ISOamsc -'⌋' => '⌋', # right floor, U+230B ISOamsc -'⟨' => '〈', # left-pointing angle bracket = bra, U+2329 ISOtech -'⟩' => '〉', # right-pointing angle bracket = ket, U+232A ISOtech -'◊' => '◊', # lozenge, U+25CA ISOpub -'♠' => '♠', # black spade suit, U+2660 ISOpub -'♣' => '♣', # black club suit = shamrock, U+2663 ISOpub -'♥' => '♥', # black heart suit = valentine, U+2665 ISOpub -'♦' => '♦', # black diamond suit, U+2666 ISOpub -'"' => '"', # quotation mark = APL quote, U+0022 ISOnum -'&' => '&', # ampersand, U+0026 ISOnum -'<' => '<', # less-than sign, U+003C ISOnum -'>' => '>', # greater-than sign, U+003E ISOnum -'Œ' => 'Œ', # latin capital ligature OE, U+0152 ISOlat2 -'œ' => 'œ', # latin small ligature oe, U+0153 ISOlat2 -'Š' => 'Š', # latin capital letter S with caron, U+0160 ISOlat2 -'š' => 'š', # latin small letter s with caron, U+0161 ISOlat2 -'Ÿ' => 'Ÿ', # latin capital letter Y with diaeresis, U+0178 ISOlat2 -'ˆ' => 'ˆ', # modifier letter circumflex accent, U+02C6 ISOpub -'˜' => '˜', # small tilde, U+02DC ISOdia -' ' => ' ', # en space, U+2002 ISOpub -' ' => ' ', # em space, U+2003 ISOpub -' ' => ' ', # thin space, U+2009 ISOpub -'‌' => '‌', # zero width non-joiner, U+200C NEW RFC 2070 -'‍' => '‍', # zero width joiner, U+200D NEW RFC 2070 -'‎' => '‎', # left-to-right mark, U+200E NEW RFC 2070 -'‏' => '‏', # right-to-left mark, U+200F NEW RFC 2070 -'–' => '–', # en dash, U+2013 ISOpub -'—' => '—', # em dash, U+2014 ISOpub -'‘' => '‘', # left single quotation mark, U+2018 ISOnum -'’' => '’', # right single quotation mark, U+2019 ISOnum -'‚' => '‚', # single low-9 quotation mark, U+201A NEW -'“' => '“', # left double quotation mark, U+201C ISOnum -'”' => '”', # right double quotation mark, U+201D ISOnum -'„' => '„', # double low-9 quotation mark, U+201E NEW -'†' => '†', # dagger, U+2020 ISOpub -'‡' => '‡', # double dagger, U+2021 ISOpub -'‰' => '‰', # per mille sign, U+2030 ISOtech -'‹' => '‹', # single left-pointing angle quotation mark, U+2039 ISO proposed -'›' => '›', # single right-pointing angle quotation mark, U+203A ISO proposed -'€' => '€', # euro sign, U+20AC NEW -''' => ''', # apostrophe = APL quote, U+0027 ISOnum -]; |