aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/mikespub/php-epub-meta/src
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/mikespub/php-epub-meta/src')
-rw-r--r--vendor/mikespub/php-epub-meta/src/App/Handler.php236
-rw-r--r--vendor/mikespub/php-epub-meta/src/App/Util.php55
-rw-r--r--vendor/mikespub/php-epub-meta/src/Contents/Nav.php63
-rw-r--r--vendor/mikespub/php-epub-meta/src/Contents/NavPoint.php109
-rw-r--r--vendor/mikespub/php-epub-meta/src/Contents/NavPointList.php65
-rw-r--r--vendor/mikespub/php-epub-meta/src/Contents/Spine.php198
-rw-r--r--vendor/mikespub/php-epub-meta/src/Contents/Toc.php63
-rw-r--r--vendor/mikespub/php-epub-meta/src/Data/Item.php193
-rw-r--r--vendor/mikespub/php-epub-meta/src/Data/Manifest.php174
-rw-r--r--vendor/mikespub/php-epub-meta/src/Dom/Element.php196
-rw-r--r--vendor/mikespub/php-epub-meta/src/Dom/XPath.php32
-rw-r--r--vendor/mikespub/php-epub-meta/src/EPub.php2126
-rw-r--r--vendor/mikespub/php-epub-meta/src/Other.php121
-rw-r--r--vendor/mikespub/php-epub-meta/src/Tools/HtmlTools.php97
-rw-r--r--vendor/mikespub/php-epub-meta/src/Tools/ZipEdit.php450
-rw-r--r--vendor/mikespub/php-epub-meta/src/Tools/ZipFile.php343
-rw-r--r--vendor/mikespub/php-epub-meta/src/Tools/htmlBlockLevelElements.php42
-rw-r--r--vendor/mikespub/php-epub-meta/src/Tools/htmlEntityMap.php260
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']) . '&amp;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 [
-'&nbsp;' => '&#160;', # no-break space = non-breaking space, U+00A0 ISOnum
-'&iexcl;' => '&#161;', # inverted exclamation mark, U+00A1 ISOnum
-'&cent;' => '&#162;', # cent sign, U+00A2 ISOnum
-'&pound;' => '&#163;', # pound sign, U+00A3 ISOnum
-'&curren;' => '&#164;', # currency sign, U+00A4 ISOnum
-'&yen;' => '&#165;', # yen sign = yuan sign, U+00A5 ISOnum
-'&brvbar;' => '&#166;', # broken bar = broken vertical bar, U+00A6 ISOnum
-'&sect;' => '&#167;', # section sign, U+00A7 ISOnum
-'&uml;' => '&#168;', # diaeresis = spacing diaeresis, U+00A8 ISOdia
-'&copy;' => '&#169;', # copyright sign, U+00A9 ISOnum
-'&ordf;' => '&#170;', # feminine ordinal indicator, U+00AA ISOnum
-'&laquo;' => '&#171;', # left-pointing double angle quotation mark = left pointing guillemet, U+00AB ISOnum
-'&not;' => '&#172;', # not sign, U+00AC ISOnum
-'&shy;' => '&#173;', # soft hyphen = discretionary hyphen, U+00AD ISOnum
-'&reg;' => '&#174;', # registered sign = registered trade mark sign, U+00AE ISOnum
-'&macr;' => '&#175;', # macron = spacing macron = overline = APL overbar, U+00AF ISOdia
-'&deg;' => '&#176;', # degree sign, U+00B0 ISOnum
-'&plusmn;' => '&#177;', # plus-minus sign = plus-or-minus sign, U+00B1 ISOnum
-'&sup2;' => '&#178;', # superscript two = superscript digit two = squared, U+00B2 ISOnum
-'&sup3;' => '&#179;', # superscript three = superscript digit three = cubed, U+00B3 ISOnum
-'&acute;' => '&#180;', # acute accent = spacing acute, U+00B4 ISOdia
-'&micro;' => '&#181;', # micro sign, U+00B5 ISOnum
-'&para;' => '&#182;', # pilcrow sign = paragraph sign, U+00B6 ISOnum
-'&middot;' => '&#183;', # middle dot = Georgian comma = Greek middle dot, U+00B7 ISOnum
-'&cedil;' => '&#184;', # cedilla = spacing cedilla, U+00B8 ISOdia
-'&sup1;' => '&#185;', # superscript one = superscript digit one, U+00B9 ISOnum
-'&ordm;' => '&#186;', # masculine ordinal indicator, U+00BA ISOnum
-'&raquo;' => '&#187;', # right-pointing double angle quotation mark = right pointing guillemet, U+00BB ISOnum
-'&frac14;' => '&#188;', # vulgar fraction one quarter = fraction one quarter, U+00BC ISOnum
-'&frac12;' => '&#189;', # vulgar fraction one half = fraction one half, U+00BD ISOnum
-'&frac34;' => '&#190;', # vulgar fraction three quarters = fraction three quarters, U+00BE ISOnum
-'&iquest;' => '&#191;', # inverted question mark = turned question mark, U+00BF ISOnum
-'&Agrave;' => '&#192;', # latin capital letter A with grave = latin capital letter A grave, U+00C0 ISOlat1
-'&Aacute;' => '&#193;', # latin capital letter A with acute, U+00C1 ISOlat1
-'&Acirc;' => '&#194;', # latin capital letter A with circumflex, U+00C2 ISOlat1
-'&Atilde;' => '&#195;', # latin capital letter A with tilde, U+00C3 ISOlat1
-'&Auml;' => '&#196;', # latin capital letter A with diaeresis, U+00C4 ISOlat1
-'&Aring;' => '&#197;', # latin capital letter A with ring above = latin capital letter A ring, U+00C5 ISOlat1
-'&AElig;' => '&#198;', # latin capital letter AE = latin capital ligature AE, U+00C6 ISOlat1
-'&Ccedil;' => '&#199;', # latin capital letter C with cedilla, U+00C7 ISOlat1
-'&Egrave;' => '&#200;', # latin capital letter E with grave, U+00C8 ISOlat1
-'&Eacute;' => '&#201;', # latin capital letter E with acute, U+00C9 ISOlat1
-'&Ecirc;' => '&#202;', # latin capital letter E with circumflex, U+00CA ISOlat1
-'&Euml;' => '&#203;', # latin capital letter E with diaeresis, U+00CB ISOlat1
-'&Igrave;' => '&#204;', # latin capital letter I with grave, U+00CC ISOlat1
-'&Iacute;' => '&#205;', # latin capital letter I with acute, U+00CD ISOlat1
-'&Icirc;' => '&#206;', # latin capital letter I with circumflex, U+00CE ISOlat1
-'&Iuml;' => '&#207;', # latin capital letter I with diaeresis, U+00CF ISOlat1
-'&ETH;' => '&#208;', # latin capital letter ETH, U+00D0 ISOlat1
-'&Ntilde;' => '&#209;', # latin capital letter N with tilde, U+00D1 ISOlat1
-'&Ograve;' => '&#210;', # latin capital letter O with grave, U+00D2 ISOlat1
-'&Oacute;' => '&#211;', # latin capital letter O with acute, U+00D3 ISOlat1
-'&Ocirc;' => '&#212;', # latin capital letter O with circumflex, U+00D4 ISOlat1
-'&Otilde;' => '&#213;', # latin capital letter O with tilde, U+00D5 ISOlat1
-'&Ouml;' => '&#214;', # latin capital letter O with diaeresis, U+00D6 ISOlat1
-'&times;' => '&#215;', # multiplication sign, U+00D7 ISOnum
-'&Oslash;' => '&#216;', # latin capital letter O with stroke = latin capital letter O slash, U+00D8 ISOlat1
-'&Ugrave;' => '&#217;', # latin capital letter U with grave, U+00D9 ISOlat1
-'&Uacute;' => '&#218;', # latin capital letter U with acute, U+00DA ISOlat1
-'&Ucirc;' => '&#219;', # latin capital letter U with circumflex, U+00DB ISOlat1
-'&Uuml;' => '&#220;', # latin capital letter U with diaeresis, U+00DC ISOlat1
-'&Yacute;' => '&#221;', # latin capital letter Y with acute, U+00DD ISOlat1
-'&THORN;' => '&#222;', # latin capital letter THORN, U+00DE ISOlat1
-'&szlig;' => '&#223;', # latin small letter sharp s = ess-zed, U+00DF ISOlat1
-'&agrave;' => '&#224;', # latin small letter a with grave = latin small letter a grave, U+00E0 ISOlat1
-'&aacute;' => '&#225;', # latin small letter a with acute, U+00E1 ISOlat1
-'&acirc;' => '&#226;', # latin small letter a with circumflex, U+00E2 ISOlat1
-'&atilde;' => '&#227;', # latin small letter a with tilde, U+00E3 ISOlat1
-'&auml;' => '&#228;', # latin small letter a with diaeresis, U+00E4 ISOlat1
-'&aring;' => '&#229;', # latin small letter a with ring above = latin small letter a ring, U+00E5 ISOlat1
-'&aelig;' => '&#230;', # latin small letter ae = latin small ligature ae, U+00E6 ISOlat1
-'&ccedil;' => '&#231;', # latin small letter c with cedilla, U+00E7 ISOlat1
-'&egrave;' => '&#232;', # latin small letter e with grave, U+00E8 ISOlat1
-'&eacute;' => '&#233;', # latin small letter e with acute, U+00E9 ISOlat1
-'&ecirc;' => '&#234;', # latin small letter e with circumflex, U+00EA ISOlat1
-'&euml;' => '&#235;', # latin small letter e with diaeresis, U+00EB ISOlat1
-'&igrave;' => '&#236;', # latin small letter i with grave, U+00EC ISOlat1
-'&iacute;' => '&#237;', # latin small letter i with acute, U+00ED ISOlat1
-'&icirc;' => '&#238;', # latin small letter i with circumflex, U+00EE ISOlat1
-'&iuml;' => '&#239;', # latin small letter i with diaeresis, U+00EF ISOlat1
-'&eth;' => '&#240;', # latin small letter eth, U+00F0 ISOlat1
-'&ntilde;' => '&#241;', # latin small letter n with tilde, U+00F1 ISOlat1
-'&ograve;' => '&#242;', # latin small letter o with grave, U+00F2 ISOlat1
-'&oacute;' => '&#243;', # latin small letter o with acute, U+00F3 ISOlat1
-'&ocirc;' => '&#244;', # latin small letter o with circumflex, U+00F4 ISOlat1
-'&otilde;' => '&#245;', # latin small letter o with tilde, U+00F5 ISOlat1
-'&ouml;' => '&#246;', # latin small letter o with diaeresis, U+00F6 ISOlat1
-'&divide;' => '&#247;', # division sign, U+00F7 ISOnum
-'&oslash;' => '&#248;', # latin small letter o with stroke, = latin small letter o slash, U+00F8 ISOlat1
-'&ugrave;' => '&#249;', # latin small letter u with grave, U+00F9 ISOlat1
-'&uacute;' => '&#250;', # latin small letter u with acute, U+00FA ISOlat1
-'&ucirc;' => '&#251;', # latin small letter u with circumflex, U+00FB ISOlat1
-'&uuml;' => '&#252;', # latin small letter u with diaeresis, U+00FC ISOlat1
-'&yacute;' => '&#253;', # latin small letter y with acute, U+00FD ISOlat1
-'&thorn;' => '&#254;', # latin small letter thorn, U+00FE ISOlat1
-'&yuml;' => '&#255;', # latin small letter y with diaeresis, U+00FF ISOlat1
-'&fnof;' => '&#402;', # latin small f with hook = function = florin, U+0192 ISOtech
-'&Alpha;' => '&#913;', # greek capital letter alpha, U+0391
-'&Beta;' => '&#914;', # greek capital letter beta, U+0392
-'&Gamma;' => '&#915;', # greek capital letter gamma, U+0393 ISOgrk3
-'&Delta;' => '&#916;', # greek capital letter delta, U+0394 ISOgrk3
-'&Epsilon;' => '&#917;', # greek capital letter epsilon, U+0395
-'&Zeta;' => '&#918;', # greek capital letter zeta, U+0396
-'&Eta;' => '&#919;', # greek capital letter eta, U+0397
-'&Theta;' => '&#920;', # greek capital letter theta, U+0398 ISOgrk3
-'&Iota;' => '&#921;', # greek capital letter iota, U+0399
-'&Kappa;' => '&#922;', # greek capital letter kappa, U+039A
-'&Lambda;' => '&#923;', # greek capital letter lambda, U+039B ISOgrk3
-'&Mu;' => '&#924;', # greek capital letter mu, U+039C
-'&Nu;' => '&#925;', # greek capital letter nu, U+039D
-'&Xi;' => '&#926;', # greek capital letter xi, U+039E ISOgrk3
-'&Omicron;' => '&#927;', # greek capital letter omicron, U+039F
-'&Pi;' => '&#928;', # greek capital letter pi, U+03A0 ISOgrk3
-'&Rho;' => '&#929;', # greek capital letter rho, U+03A1
-'&Sigma;' => '&#931;', # greek capital letter sigma, U+03A3 ISOgrk3
-'&Tau;' => '&#932;', # greek capital letter tau, U+03A4
-'&Upsilon;' => '&#933;', # greek capital letter upsilon, U+03A5 ISOgrk3
-'&Phi;' => '&#934;', # greek capital letter phi, U+03A6 ISOgrk3
-'&Chi;' => '&#935;', # greek capital letter chi, U+03A7
-'&Psi;' => '&#936;', # greek capital letter psi, U+03A8 ISOgrk3
-'&Omega;' => '&#937;', # greek capital letter omega, U+03A9 ISOgrk3
-'&alpha;' => '&#945;', # greek small letter alpha, U+03B1 ISOgrk3
-'&beta;' => '&#946;', # greek small letter beta, U+03B2 ISOgrk3
-'&gamma;' => '&#947;', # greek small letter gamma, U+03B3 ISOgrk3
-'&delta;' => '&#948;', # greek small letter delta, U+03B4 ISOgrk3
-'&epsilon;' => '&#949;', # greek small letter epsilon, U+03B5 ISOgrk3
-'&zeta;' => '&#950;', # greek small letter zeta, U+03B6 ISOgrk3
-'&eta;' => '&#951;', # greek small letter eta, U+03B7 ISOgrk3
-'&theta;' => '&#952;', # greek small letter theta, U+03B8 ISOgrk3
-'&iota;' => '&#953;', # greek small letter iota, U+03B9 ISOgrk3
-'&kappa;' => '&#954;', # greek small letter kappa, U+03BA ISOgrk3
-'&lambda;' => '&#955;', # greek small letter lambda, U+03BB ISOgrk3
-'&mu;' => '&#956;', # greek small letter mu, U+03BC ISOgrk3
-'&nu;' => '&#957;', # greek small letter nu, U+03BD ISOgrk3
-'&xi;' => '&#958;', # greek small letter xi, U+03BE ISOgrk3
-'&omicron;' => '&#959;', # greek small letter omicron, U+03BF NEW
-'&pi;' => '&#960;', # greek small letter pi, U+03C0 ISOgrk3
-'&rho;' => '&#961;', # greek small letter rho, U+03C1 ISOgrk3
-'&sigmaf;' => '&#962;', # greek small letter final sigma, U+03C2 ISOgrk3
-'&sigma;' => '&#963;', # greek small letter sigma, U+03C3 ISOgrk3
-'&tau;' => '&#964;', # greek small letter tau, U+03C4 ISOgrk3
-'&upsilon;' => '&#965;', # greek small letter upsilon, U+03C5 ISOgrk3
-'&phi;' => '&#966;', # greek small letter phi, U+03C6 ISOgrk3
-'&chi;' => '&#967;', # greek small letter chi, U+03C7 ISOgrk3
-'&psi;' => '&#968;', # greek small letter psi, U+03C8 ISOgrk3
-'&omega;' => '&#969;', # greek small letter omega, U+03C9 ISOgrk3
-'&thetasym;' => '&#977;', # greek small letter theta symbol, U+03D1 NEW
-'&upsih;' => '&#978;', # greek upsilon with hook symbol, U+03D2 NEW
-'&piv;' => '&#982;', # greek pi symbol, U+03D6 ISOgrk3
-'&bull;' => '&#8226;', # bullet = black small circle, U+2022 ISOpub
-'&hellip;' => '&#8230;', # horizontal ellipsis = three dot leader, U+2026 ISOpub
-'&prime;' => '&#8242;', # prime = minutes = feet, U+2032 ISOtech
-'&Prime;' => '&#8243;', # double prime = seconds = inches, U+2033 ISOtech
-'&oline;' => '&#8254;', # overline = spacing overscore, U+203E NEW
-'&frasl;' => '&#8260;', # fraction slash, U+2044 NEW
-'&weierp;' => '&#8472;', # script capital P = power set = Weierstrass p, U+2118 ISOamso
-'&image;' => '&#8465;', # blackletter capital I = imaginary part, U+2111 ISOamso
-'&real;' => '&#8476;', # blackletter capital R = real part symbol, U+211C ISOamso
-'&trade;' => '&#8482;', # trade mark sign, U+2122 ISOnum
-'&alefsym;' => '&#8501;', # alef symbol = first transfinite cardinal, U+2135 NEW
-'&larr;' => '&#8592;', # leftwards arrow, U+2190 ISOnum
-'&uarr;' => '&#8593;', # upwards arrow, U+2191 ISOnum
-'&rarr;' => '&#8594;', # rightwards arrow, U+2192 ISOnum
-'&darr;' => '&#8595;', # downwards arrow, U+2193 ISOnum
-'&harr;' => '&#8596;', # left right arrow, U+2194 ISOamsa
-'&crarr;' => '&#8629;', # downwards arrow with corner leftwards = carriage return, U+21B5 NEW
-'&lArr;' => '&#8656;', # leftwards double arrow, U+21D0 ISOtech
-'&uArr;' => '&#8657;', # upwards double arrow, U+21D1 ISOamsa
-'&rArr;' => '&#8658;', # rightwards double arrow, U+21D2 ISOtech
-'&dArr;' => '&#8659;', # downwards double arrow, U+21D3 ISOamsa
-'&hArr;' => '&#8660;', # left right double arrow, U+21D4 ISOamsa
-'&forall;' => '&#8704;', # for all, U+2200 ISOtech
-'&part;' => '&#8706;', # partial differential, U+2202 ISOtech
-'&exist;' => '&#8707;', # there exists, U+2203 ISOtech
-'&empty;' => '&#8709;', # empty set = null set = diameter, U+2205 ISOamso
-'&nabla;' => '&#8711;', # nabla = backward difference, U+2207 ISOtech
-'&isin;' => '&#8712;', # element of, U+2208 ISOtech
-'&notin;' => '&#8713;', # not an element of, U+2209 ISOtech
-'&ni;' => '&#8715;', # contains as member, U+220B ISOtech
-'&prod;' => '&#8719;', # n-ary product = product sign, U+220F ISOamsb
-'&sum;' => '&#8721;', # n-ary sumation, U+2211 ISOamsb
-'&minus;' => '&#8722;', # minus sign, U+2212 ISOtech
-'&lowast;' => '&#8727;', # asterisk operator, U+2217 ISOtech
-'&radic;' => '&#8730;', # square root = radical sign, U+221A ISOtech
-'&prop;' => '&#8733;', # proportional to, U+221D ISOtech
-'&infin;' => '&#8734;', # infinity, U+221E ISOtech
-'&ang;' => '&#8736;', # angle, U+2220 ISOamso
-'&and;' => '&#8743;', # logical and = wedge, U+2227 ISOtech
-'&or;' => '&#8744;', # logical or = vee, U+2228 ISOtech
-'&cap;' => '&#8745;', # intersection = cap, U+2229 ISOtech
-'&cup;' => '&#8746;', # union = cup, U+222A ISOtech
-'&int;' => '&#8747;', # integral, U+222B ISOtech
-'&there4;' => '&#8756;', # therefore, U+2234 ISOtech
-'&sim;' => '&#8764;', # tilde operator = varies with = similar to, U+223C ISOtech
-'&cong;' => '&#8773;', # approximately equal to, U+2245 ISOtech
-'&asymp;' => '&#8776;', # almost equal to = asymptotic to, U+2248 ISOamsr
-'&ne;' => '&#8800;', # not equal to, U+2260 ISOtech
-'&equiv;' => '&#8801;', # identical to, U+2261 ISOtech
-'&le;' => '&#8804;', # less-than or equal to, U+2264 ISOtech
-'&ge;' => '&#8805;', # greater-than or equal to, U+2265 ISOtech
-'&sub;' => '&#8834;', # subset of, U+2282 ISOtech
-'&sup;' => '&#8835;', # superset of, U+2283 ISOtech
-'&nsub;' => '&#8836;', # not a subset of, U+2284 ISOamsn
-'&sube;' => '&#8838;', # subset of or equal to, U+2286 ISOtech
-'&supe;' => '&#8839;', # superset of or equal to, U+2287 ISOtech
-'&oplus;' => '&#8853;', # circled plus = direct sum, U+2295 ISOamsb
-'&otimes;' => '&#8855;', # circled times = vector product, U+2297 ISOamsb
-'&perp;' => '&#8869;', # up tack = orthogonal to = perpendicular, U+22A5 ISOtech
-'&sdot;' => '&#8901;', # dot operator, U+22C5 ISOamsb
-'&lceil;' => '&#8968;', # left ceiling = apl upstile, U+2308 ISOamsc
-'&rceil;' => '&#8969;', # right ceiling, U+2309 ISOamsc
-'&lfloor;' => '&#8970;', # left floor = apl downstile, U+230A ISOamsc
-'&rfloor;' => '&#8971;', # right floor, U+230B ISOamsc
-'&lang;' => '&#9001;', # left-pointing angle bracket = bra, U+2329 ISOtech
-'&rang;' => '&#9002;', # right-pointing angle bracket = ket, U+232A ISOtech
-'&loz;' => '&#9674;', # lozenge, U+25CA ISOpub
-'&spades;' => '&#9824;', # black spade suit, U+2660 ISOpub
-'&clubs;' => '&#9827;', # black club suit = shamrock, U+2663 ISOpub
-'&hearts;' => '&#9829;', # black heart suit = valentine, U+2665 ISOpub
-'&diams;' => '&#9830;', # black diamond suit, U+2666 ISOpub
-'&quot;' => '&#34;', # quotation mark = APL quote, U+0022 ISOnum
-'&amp;' => '&#38;', # ampersand, U+0026 ISOnum
-'&lt;' => '&#60;', # less-than sign, U+003C ISOnum
-'&gt;' => '&#62;', # greater-than sign, U+003E ISOnum
-'&OElig;' => '&#338;', # latin capital ligature OE, U+0152 ISOlat2
-'&oelig;' => '&#339;', # latin small ligature oe, U+0153 ISOlat2
-'&Scaron;' => '&#352;', # latin capital letter S with caron, U+0160 ISOlat2
-'&scaron;' => '&#353;', # latin small letter s with caron, U+0161 ISOlat2
-'&Yuml;' => '&#376;', # latin capital letter Y with diaeresis, U+0178 ISOlat2
-'&circ;' => '&#710;', # modifier letter circumflex accent, U+02C6 ISOpub
-'&tilde;' => '&#732;', # small tilde, U+02DC ISOdia
-'&ensp;' => '&#8194;', # en space, U+2002 ISOpub
-'&emsp;' => '&#8195;', # em space, U+2003 ISOpub
-'&thinsp;' => '&#8201;', # thin space, U+2009 ISOpub
-'&zwnj;' => '&#8204;', # zero width non-joiner, U+200C NEW RFC 2070
-'&zwj;' => '&#8205;', # zero width joiner, U+200D NEW RFC 2070
-'&lrm;' => '&#8206;', # left-to-right mark, U+200E NEW RFC 2070
-'&rlm;' => '&#8207;', # right-to-left mark, U+200F NEW RFC 2070
-'&ndash;' => '&#8211;', # en dash, U+2013 ISOpub
-'&mdash;' => '&#8212;', # em dash, U+2014 ISOpub
-'&lsquo;' => '&#8216;', # left single quotation mark, U+2018 ISOnum
-'&rsquo;' => '&#8217;', # right single quotation mark, U+2019 ISOnum
-'&sbquo;' => '&#8218;', # single low-9 quotation mark, U+201A NEW
-'&ldquo;' => '&#8220;', # left double quotation mark, U+201C ISOnum
-'&rdquo;' => '&#8221;', # right double quotation mark, U+201D ISOnum
-'&bdquo;' => '&#8222;', # double low-9 quotation mark, U+201E NEW
-'&dagger;' => '&#8224;', # dagger, U+2020 ISOpub
-'&Dagger;' => '&#8225;', # double dagger, U+2021 ISOpub
-'&permil;' => '&#8240;', # per mille sign, U+2030 ISOtech
-'&lsaquo;' => '&#8249;', # single left-pointing angle quotation mark, U+2039 ISO proposed
-'&rsaquo;' => '&#8250;', # single right-pointing angle quotation mark, U+203A ISO proposed
-'&euro;' => '&#8364;', # euro sign, U+20AC NEW
-'&apos;' => '&#39;', # apostrophe = APL quote, U+0027 ISOnum
-];