diff options
Diffstat (limited to 'vendor/mikespub/php-epub-meta/src/Tools')
5 files changed, 1192 insertions, 0 deletions
diff --git a/vendor/mikespub/php-epub-meta/src/Tools/HtmlTools.php b/vendor/mikespub/php-epub-meta/src/Tools/HtmlTools.php new file mode 100644 index 000000000..f905d265a --- /dev/null +++ b/vendor/mikespub/php-epub-meta/src/Tools/HtmlTools.php @@ -0,0 +1,97 @@ +<?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 new file mode 100644 index 000000000..ff9f607cf --- /dev/null +++ b/vendor/mikespub/php-epub-meta/src/Tools/ZipEdit.php @@ -0,0 +1,450 @@ +<?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 new file mode 100644 index 000000000..b083b0c4f --- /dev/null +++ b/vendor/mikespub/php-epub-meta/src/Tools/ZipFile.php @@ -0,0 +1,343 @@ +<?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 new file mode 100644 index 000000000..6e96a6b0f --- /dev/null +++ b/vendor/mikespub/php-epub-meta/src/Tools/htmlBlockLevelElements.php @@ -0,0 +1,42 @@ +<?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 new file mode 100644 index 000000000..6bc3f4653 --- /dev/null +++ b/vendor/mikespub/php-epub-meta/src/Tools/htmlEntityMap.php @@ -0,0 +1,260 @@ +<?php +/** + * Maps every HTML named entity to its numeric equivalent. + * Source: http://stackoverflow.com/questions/11176752/converting-named-html-entities-to-numeric-html-entities#answer-11179875 + */ +return [ +' ' => ' ', # no-break space = non-breaking space, U+00A0 ISOnum +'¡' => '¡', # inverted exclamation mark, U+00A1 ISOnum +'¢' => '¢', # cent sign, U+00A2 ISOnum +'£' => '£', # pound sign, U+00A3 ISOnum +'¤' => '¤', # currency sign, U+00A4 ISOnum +'¥' => '¥', # yen sign = yuan sign, U+00A5 ISOnum +'¦' => '¦', # broken bar = broken vertical bar, U+00A6 ISOnum +'§' => '§', # section sign, U+00A7 ISOnum +'¨' => '¨', # diaeresis = spacing diaeresis, U+00A8 ISOdia +'©' => '©', # copyright sign, U+00A9 ISOnum +'ª' => 'ª', # feminine ordinal indicator, U+00AA ISOnum +'«' => '«', # left-pointing double angle quotation mark = left pointing guillemet, U+00AB ISOnum +'¬' => '¬', # not sign, U+00AC ISOnum +'­' => '­', # soft hyphen = discretionary hyphen, U+00AD ISOnum +'®' => '®', # registered sign = registered trade mark sign, U+00AE ISOnum +'¯' => '¯', # macron = spacing macron = overline = APL overbar, U+00AF ISOdia +'°' => '°', # degree sign, U+00B0 ISOnum +'±' => '±', # plus-minus sign = plus-or-minus sign, U+00B1 ISOnum +'²' => '²', # superscript two = superscript digit two = squared, U+00B2 ISOnum +'³' => '³', # superscript three = superscript digit three = cubed, U+00B3 ISOnum +'´' => '´', # acute accent = spacing acute, U+00B4 ISOdia +'µ' => 'µ', # micro sign, U+00B5 ISOnum +'¶' => '¶', # pilcrow sign = paragraph sign, U+00B6 ISOnum +'·' => '·', # middle dot = Georgian comma = Greek middle dot, U+00B7 ISOnum +'¸' => '¸', # cedilla = spacing cedilla, U+00B8 ISOdia +'¹' => '¹', # superscript one = superscript digit one, U+00B9 ISOnum +'º' => 'º', # masculine ordinal indicator, U+00BA ISOnum +'»' => '»', # right-pointing double angle quotation mark = right pointing guillemet, U+00BB ISOnum +'¼' => '¼', # vulgar fraction one quarter = fraction one quarter, U+00BC ISOnum +'½' => '½', # vulgar fraction one half = fraction one half, U+00BD ISOnum +'¾' => '¾', # vulgar fraction three quarters = fraction three quarters, U+00BE ISOnum +'¿' => '¿', # inverted question mark = turned question mark, U+00BF ISOnum +'À' => 'À', # latin capital letter A with grave = latin capital letter A grave, U+00C0 ISOlat1 +'Á' => 'Á', # latin capital letter A with acute, U+00C1 ISOlat1 +'Â' => 'Â', # latin capital letter A with circumflex, U+00C2 ISOlat1 +'Ã' => 'Ã', # latin capital letter A with tilde, U+00C3 ISOlat1 +'Ä' => 'Ä', # latin capital letter A with diaeresis, U+00C4 ISOlat1 +'Å' => 'Å', # latin capital letter A with ring above = latin capital letter A ring, U+00C5 ISOlat1 +'Æ' => 'Æ', # latin capital letter AE = latin capital ligature AE, U+00C6 ISOlat1 +'Ç' => 'Ç', # latin capital letter C with cedilla, U+00C7 ISOlat1 +'È' => 'È', # latin capital letter E with grave, U+00C8 ISOlat1 +'É' => 'É', # latin capital letter E with acute, U+00C9 ISOlat1 +'Ê' => 'Ê', # latin capital letter E with circumflex, U+00CA ISOlat1 +'Ë' => 'Ë', # latin capital letter E with diaeresis, U+00CB ISOlat1 +'Ì' => 'Ì', # latin capital letter I with grave, U+00CC ISOlat1 +'Í' => 'Í', # latin capital letter I with acute, U+00CD ISOlat1 +'Î' => 'Î', # latin capital letter I with circumflex, U+00CE ISOlat1 +'Ï' => 'Ï', # latin capital letter I with diaeresis, U+00CF ISOlat1 +'Ð' => 'Ð', # latin capital letter ETH, U+00D0 ISOlat1 +'Ñ' => 'Ñ', # latin capital letter N with tilde, U+00D1 ISOlat1 +'Ò' => 'Ò', # latin capital letter O with grave, U+00D2 ISOlat1 +'Ó' => 'Ó', # latin capital letter O with acute, U+00D3 ISOlat1 +'Ô' => 'Ô', # latin capital letter O with circumflex, U+00D4 ISOlat1 +'Õ' => 'Õ', # latin capital letter O with tilde, U+00D5 ISOlat1 +'Ö' => 'Ö', # latin capital letter O with diaeresis, U+00D6 ISOlat1 +'×' => '×', # multiplication sign, U+00D7 ISOnum +'Ø' => 'Ø', # latin capital letter O with stroke = latin capital letter O slash, U+00D8 ISOlat1 +'Ù' => 'Ù', # latin capital letter U with grave, U+00D9 ISOlat1 +'Ú' => 'Ú', # latin capital letter U with acute, U+00DA ISOlat1 +'Û' => 'Û', # latin capital letter U with circumflex, U+00DB ISOlat1 +'Ü' => 'Ü', # latin capital letter U with diaeresis, U+00DC ISOlat1 +'Ý' => 'Ý', # latin capital letter Y with acute, U+00DD ISOlat1 +'Þ' => 'Þ', # latin capital letter THORN, U+00DE ISOlat1 +'ß' => 'ß', # latin small letter sharp s = ess-zed, U+00DF ISOlat1 +'à' => 'à', # latin small letter a with grave = latin small letter a grave, U+00E0 ISOlat1 +'á' => 'á', # latin small letter a with acute, U+00E1 ISOlat1 +'â' => 'â', # latin small letter a with circumflex, U+00E2 ISOlat1 +'ã' => 'ã', # latin small letter a with tilde, U+00E3 ISOlat1 +'ä' => 'ä', # latin small letter a with diaeresis, U+00E4 ISOlat1 +'å' => 'å', # latin small letter a with ring above = latin small letter a ring, U+00E5 ISOlat1 +'æ' => 'æ', # latin small letter ae = latin small ligature ae, U+00E6 ISOlat1 +'ç' => 'ç', # latin small letter c with cedilla, U+00E7 ISOlat1 +'è' => 'è', # latin small letter e with grave, U+00E8 ISOlat1 +'é' => 'é', # latin small letter e with acute, U+00E9 ISOlat1 +'ê' => 'ê', # latin small letter e with circumflex, U+00EA ISOlat1 +'ë' => 'ë', # latin small letter e with diaeresis, U+00EB ISOlat1 +'ì' => 'ì', # latin small letter i with grave, U+00EC ISOlat1 +'í' => 'í', # latin small letter i with acute, U+00ED ISOlat1 +'î' => 'î', # latin small letter i with circumflex, U+00EE ISOlat1 +'ï' => 'ï', # latin small letter i with diaeresis, U+00EF ISOlat1 +'ð' => 'ð', # latin small letter eth, U+00F0 ISOlat1 +'ñ' => 'ñ', # latin small letter n with tilde, U+00F1 ISOlat1 +'ò' => 'ò', # latin small letter o with grave, U+00F2 ISOlat1 +'ó' => 'ó', # latin small letter o with acute, U+00F3 ISOlat1 +'ô' => 'ô', # latin small letter o with circumflex, U+00F4 ISOlat1 +'õ' => 'õ', # latin small letter o with tilde, U+00F5 ISOlat1 +'ö' => 'ö', # latin small letter o with diaeresis, U+00F6 ISOlat1 +'÷' => '÷', # division sign, U+00F7 ISOnum +'ø' => 'ø', # latin small letter o with stroke, = latin small letter o slash, U+00F8 ISOlat1 +'ù' => 'ù', # latin small letter u with grave, U+00F9 ISOlat1 +'ú' => 'ú', # latin small letter u with acute, U+00FA ISOlat1 +'û' => 'û', # latin small letter u with circumflex, U+00FB ISOlat1 +'ü' => 'ü', # latin small letter u with diaeresis, U+00FC ISOlat1 +'ý' => 'ý', # latin small letter y with acute, U+00FD ISOlat1 +'þ' => 'þ', # latin small letter thorn, U+00FE ISOlat1 +'ÿ' => 'ÿ', # latin small letter y with diaeresis, U+00FF ISOlat1 +'ƒ' => 'ƒ', # latin small f with hook = function = florin, U+0192 ISOtech +'Α' => 'Α', # greek capital letter alpha, U+0391 +'Β' => 'Β', # greek capital letter beta, U+0392 +'Γ' => 'Γ', # greek capital letter gamma, U+0393 ISOgrk3 +'Δ' => 'Δ', # greek capital letter delta, U+0394 ISOgrk3 +'Ε' => 'Ε', # greek capital letter epsilon, U+0395 +'Ζ' => 'Ζ', # greek capital letter zeta, U+0396 +'Η' => 'Η', # greek capital letter eta, U+0397 +'Θ' => 'Θ', # greek capital letter theta, U+0398 ISOgrk3 +'Ι' => 'Ι', # greek capital letter iota, U+0399 +'Κ' => 'Κ', # greek capital letter kappa, U+039A +'Λ' => 'Λ', # greek capital letter lambda, U+039B ISOgrk3 +'Μ' => 'Μ', # greek capital letter mu, U+039C +'Ν' => 'Ν', # greek capital letter nu, U+039D +'Ξ' => 'Ξ', # greek capital letter xi, U+039E ISOgrk3 +'Ο' => 'Ο', # greek capital letter omicron, U+039F +'Π' => 'Π', # greek capital letter pi, U+03A0 ISOgrk3 +'Ρ' => 'Ρ', # greek capital letter rho, U+03A1 +'Σ' => 'Σ', # greek capital letter sigma, U+03A3 ISOgrk3 +'Τ' => 'Τ', # greek capital letter tau, U+03A4 +'Υ' => 'Υ', # greek capital letter upsilon, U+03A5 ISOgrk3 +'Φ' => 'Φ', # greek capital letter phi, U+03A6 ISOgrk3 +'Χ' => 'Χ', # greek capital letter chi, U+03A7 +'Ψ' => 'Ψ', # greek capital letter psi, U+03A8 ISOgrk3 +'Ω' => 'Ω', # greek capital letter omega, U+03A9 ISOgrk3 +'α' => 'α', # greek small letter alpha, U+03B1 ISOgrk3 +'β' => 'β', # greek small letter beta, U+03B2 ISOgrk3 +'γ' => 'γ', # greek small letter gamma, U+03B3 ISOgrk3 +'δ' => 'δ', # greek small letter delta, U+03B4 ISOgrk3 +'ε' => 'ε', # greek small letter epsilon, U+03B5 ISOgrk3 +'ζ' => 'ζ', # greek small letter zeta, U+03B6 ISOgrk3 +'η' => 'η', # greek small letter eta, U+03B7 ISOgrk3 +'θ' => 'θ', # greek small letter theta, U+03B8 ISOgrk3 +'ι' => 'ι', # greek small letter iota, U+03B9 ISOgrk3 +'κ' => 'κ', # greek small letter kappa, U+03BA ISOgrk3 +'λ' => 'λ', # greek small letter lambda, U+03BB ISOgrk3 +'μ' => 'μ', # greek small letter mu, U+03BC ISOgrk3 +'ν' => 'ν', # greek small letter nu, U+03BD ISOgrk3 +'ξ' => 'ξ', # greek small letter xi, U+03BE ISOgrk3 +'ο' => 'ο', # greek small letter omicron, U+03BF NEW +'π' => 'π', # greek small letter pi, U+03C0 ISOgrk3 +'ρ' => 'ρ', # greek small letter rho, U+03C1 ISOgrk3 +'ς' => 'ς', # greek small letter final sigma, U+03C2 ISOgrk3 +'σ' => 'σ', # greek small letter sigma, U+03C3 ISOgrk3 +'τ' => 'τ', # greek small letter tau, U+03C4 ISOgrk3 +'υ' => 'υ', # greek small letter upsilon, U+03C5 ISOgrk3 +'φ' => 'φ', # greek small letter phi, U+03C6 ISOgrk3 +'χ' => 'χ', # greek small letter chi, U+03C7 ISOgrk3 +'ψ' => 'ψ', # greek small letter psi, U+03C8 ISOgrk3 +'ω' => 'ω', # greek small letter omega, U+03C9 ISOgrk3 +'ϑ' => 'ϑ', # greek small letter theta symbol, U+03D1 NEW +'ϒ' => 'ϒ', # greek upsilon with hook symbol, U+03D2 NEW +'ϖ' => 'ϖ', # greek pi symbol, U+03D6 ISOgrk3 +'•' => '•', # bullet = black small circle, U+2022 ISOpub +'…' => '…', # horizontal ellipsis = three dot leader, U+2026 ISOpub +'′' => '′', # prime = minutes = feet, U+2032 ISOtech +'″' => '″', # double prime = seconds = inches, U+2033 ISOtech +'‾' => '‾', # overline = spacing overscore, U+203E NEW +'⁄' => '⁄', # fraction slash, U+2044 NEW +'℘' => '℘', # script capital P = power set = Weierstrass p, U+2118 ISOamso +'ℑ' => 'ℑ', # blackletter capital I = imaginary part, U+2111 ISOamso +'ℜ' => 'ℜ', # blackletter capital R = real part symbol, U+211C ISOamso +'™' => '™', # trade mark sign, U+2122 ISOnum +'ℵ' => 'ℵ', # alef symbol = first transfinite cardinal, U+2135 NEW +'←' => '←', # leftwards arrow, U+2190 ISOnum +'↑' => '↑', # upwards arrow, U+2191 ISOnum +'→' => '→', # rightwards arrow, U+2192 ISOnum +'↓' => '↓', # downwards arrow, U+2193 ISOnum +'↔' => '↔', # left right arrow, U+2194 ISOamsa +'↵' => '↵', # downwards arrow with corner leftwards = carriage return, U+21B5 NEW +'⇐' => '⇐', # leftwards double arrow, U+21D0 ISOtech +'⇑' => '⇑', # upwards double arrow, U+21D1 ISOamsa +'⇒' => '⇒', # rightwards double arrow, U+21D2 ISOtech +'⇓' => '⇓', # downwards double arrow, U+21D3 ISOamsa +'⇔' => '⇔', # left right double arrow, U+21D4 ISOamsa +'∀' => '∀', # for all, U+2200 ISOtech +'∂' => '∂', # partial differential, U+2202 ISOtech +'∃' => '∃', # there exists, U+2203 ISOtech +'∅' => '∅', # empty set = null set = diameter, U+2205 ISOamso +'∇' => '∇', # nabla = backward difference, U+2207 ISOtech +'∈' => '∈', # element of, U+2208 ISOtech +'∉' => '∉', # not an element of, U+2209 ISOtech +'∋' => '∋', # contains as member, U+220B ISOtech +'∏' => '∏', # n-ary product = product sign, U+220F ISOamsb +'∑' => '∑', # n-ary sumation, U+2211 ISOamsb +'−' => '−', # minus sign, U+2212 ISOtech +'∗' => '∗', # asterisk operator, U+2217 ISOtech +'√' => '√', # square root = radical sign, U+221A ISOtech +'∝' => '∝', # proportional to, U+221D ISOtech +'∞' => '∞', # infinity, U+221E ISOtech +'∠' => '∠', # angle, U+2220 ISOamso +'∧' => '∧', # logical and = wedge, U+2227 ISOtech +'∨' => '∨', # logical or = vee, U+2228 ISOtech +'∩' => '∩', # intersection = cap, U+2229 ISOtech +'∪' => '∪', # union = cup, U+222A ISOtech +'∫' => '∫', # integral, U+222B ISOtech +'∴' => '∴', # therefore, U+2234 ISOtech +'∼' => '∼', # tilde operator = varies with = similar to, U+223C ISOtech +'≅' => '≅', # approximately equal to, U+2245 ISOtech +'≈' => '≈', # almost equal to = asymptotic to, U+2248 ISOamsr +'≠' => '≠', # not equal to, U+2260 ISOtech +'≡' => '≡', # identical to, U+2261 ISOtech +'≤' => '≤', # less-than or equal to, U+2264 ISOtech +'≥' => '≥', # greater-than or equal to, U+2265 ISOtech +'⊂' => '⊂', # subset of, U+2282 ISOtech +'⊃' => '⊃', # superset of, U+2283 ISOtech +'⊄' => '⊄', # not a subset of, U+2284 ISOamsn +'⊆' => '⊆', # subset of or equal to, U+2286 ISOtech +'⊇' => '⊇', # superset of or equal to, U+2287 ISOtech +'⊕' => '⊕', # circled plus = direct sum, U+2295 ISOamsb +'⊗' => '⊗', # circled times = vector product, U+2297 ISOamsb +'⊥' => '⊥', # up tack = orthogonal to = perpendicular, U+22A5 ISOtech +'⋅' => '⋅', # dot operator, U+22C5 ISOamsb +'⌈' => '⌈', # left ceiling = apl upstile, U+2308 ISOamsc +'⌉' => '⌉', # right ceiling, U+2309 ISOamsc +'⌊' => '⌊', # left floor = apl downstile, U+230A ISOamsc +'⌋' => '⌋', # right floor, U+230B ISOamsc +'⟨' => '〈', # left-pointing angle bracket = bra, U+2329 ISOtech +'⟩' => '〉', # right-pointing angle bracket = ket, U+232A ISOtech +'◊' => '◊', # lozenge, U+25CA ISOpub +'♠' => '♠', # black spade suit, U+2660 ISOpub +'♣' => '♣', # black club suit = shamrock, U+2663 ISOpub +'♥' => '♥', # black heart suit = valentine, U+2665 ISOpub +'♦' => '♦', # black diamond suit, U+2666 ISOpub +'"' => '"', # quotation mark = APL quote, U+0022 ISOnum +'&' => '&', # ampersand, U+0026 ISOnum +'<' => '<', # less-than sign, U+003C ISOnum +'>' => '>', # greater-than sign, U+003E ISOnum +'Œ' => 'Œ', # latin capital ligature OE, U+0152 ISOlat2 +'œ' => 'œ', # latin small ligature oe, U+0153 ISOlat2 +'Š' => 'Š', # latin capital letter S with caron, U+0160 ISOlat2 +'š' => 'š', # latin small letter s with caron, U+0161 ISOlat2 +'Ÿ' => 'Ÿ', # latin capital letter Y with diaeresis, U+0178 ISOlat2 +'ˆ' => 'ˆ', # modifier letter circumflex accent, U+02C6 ISOpub +'˜' => '˜', # small tilde, U+02DC ISOdia +' ' => ' ', # en space, U+2002 ISOpub +' ' => ' ', # em space, U+2003 ISOpub +' ' => ' ', # thin space, U+2009 ISOpub +'‌' => '‌', # zero width non-joiner, U+200C NEW RFC 2070 +'‍' => '‍', # zero width joiner, U+200D NEW RFC 2070 +'‎' => '‎', # left-to-right mark, U+200E NEW RFC 2070 +'‏' => '‏', # right-to-left mark, U+200F NEW RFC 2070 +'–' => '–', # en dash, U+2013 ISOpub +'—' => '—', # em dash, U+2014 ISOpub +'‘' => '‘', # left single quotation mark, U+2018 ISOnum +'’' => '’', # right single quotation mark, U+2019 ISOnum +'‚' => '‚', # single low-9 quotation mark, U+201A NEW +'“' => '“', # left double quotation mark, U+201C ISOnum +'”' => '”', # right double quotation mark, U+201D ISOnum +'„' => '„', # double low-9 quotation mark, U+201E NEW +'†' => '†', # dagger, U+2020 ISOpub +'‡' => '‡', # double dagger, U+2021 ISOpub +'‰' => '‰', # per mille sign, U+2030 ISOtech +'‹' => '‹', # single left-pointing angle quotation mark, U+2039 ISO proposed +'›' => '›', # single right-pointing angle quotation mark, U+203A ISO proposed +'€' => '€', # euro sign, U+20AC NEW +''' => ''', # apostrophe = APL quote, U+0027 ISOnum +]; |