<?php /** * ePub NCX file structure * * @author A. Grandt <php@grandt.com> * @copyright 2009-2014 A. Grandt * @license GNU LGPL, Attribution required for commercial implementations, requested for everything else. * @version 3.20 */ class Ncx { const _VERSION = 3.20; const MIMETYPE = "application/x-dtbncx+xml"; private $bookVersion = EPub::BOOK_VERSION_EPUB2; private $navMap = NULL; private $uid = NULL; private $meta = array(); private $docTitle = NULL; private $docAuthor = NULL; private $currentLevel = NULL; private $lastLevel = NULL; private $languageCode = "en"; private $writingDirection = EPub::DIRECTION_LEFT_TO_RIGHT; public $chapterList = array(); public $referencesTitle = "Guide"; public $referencesClass = "references"; public $referencesId = "references"; public $referencesList = array(); public $referencesName = array(); public $referencesOrder = NULL; /** * Class constructor. * * @param string $uid * @param string $docTitle * @param string $docAuthor * @param string $languageCode * @param string $writingDirection */ function __construct($uid = NULL, $docTitle = NULL, $docAuthor = NULL, $languageCode = "en", $writingDirection = EPub::DIRECTION_LEFT_TO_RIGHT) { $this->navMap = new NavMap($writingDirection); $this->currentLevel = $this->navMap; $this->setUid($uid); $this->setDocTitle($docTitle); $this->setDocAuthor($docAuthor); $this->setLanguageCode($languageCode); $this->setWritingDirection($writingDirection); } /** * Class destructor * * @return void */ function __destruct() { unset($this->bookVersion, $this->navMap, $this->uid, $this->meta); unset($this->docTitle, $this->docAuthor, $this->currentLevel, $this->lastLevel); unset($this->languageCode, $this->writingDirection, $this->chapterList, $this->referencesTitle); unset($this->referencesClass, $this->referencesId, $this->referencesList, $this->referencesName); unset($this->referencesOrder); } /** * * Enter description here ... * * @param string $bookVersion */ function setVersion($bookVersion) { $this->bookVersion = is_string($bookVersion) ? trim($bookVersion) : EPub::BOOK_VERSION_EPUB2; } /** * * @return bool TRUE if the book is set to type ePub 2 */ function isEPubVersion2() { return $this->bookVersion === EPub::BOOK_VERSION_EPUB2; } /** * * Enter description here ... * * @param string $uid */ function setUid($uid) { $this->uid = is_string($uid) ? trim($uid) : NULL; } /** * * Enter description here ... * * @param string $docTitle */ function setDocTitle($docTitle) { $this->docTitle = is_string($docTitle) ? trim($docTitle) : NULL; } /** * * Enter description here ... * * @param string $docAuthor */ function setDocAuthor($docAuthor) { $this->docAuthor = is_string($docAuthor) ? trim($docAuthor) : NULL; } /** * * Enter description here ... * * @param string $languageCode */ function setLanguageCode($languageCode) { $this->languageCode = is_string($languageCode) ? trim($languageCode) : "en"; } /** * * Enter description here ... * * @param string $writingDirection */ function setWritingDirection($writingDirection) { $this->writingDirection = is_string($writingDirection) ? trim($writingDirection) : EPub::DIRECTION_LEFT_TO_RIGHT; } /** * * Enter description here ... * * @param NavMap $navMap */ function setNavMap($navMap) { if ($navMap != NULL && is_object($navMap) && get_class($navMap) === "NavMap") { $this->navMap = $navMap; } } /** * Add one chapter level. * * Subsequent chapters will be added to this level. * * @param string $navTitle * @param string $navId * @param string $navClass * @param string $isNavHidden * @param string $writingDirection * @return NavPoint */ function subLevel($navTitle = NULL, $navId = NULL, $navClass = NULL, $isNavHidden = FALSE, $writingDirection = NULL) { $navPoint = FALSE; if (isset($navTitle) && isset($navClass)) { $navPoint = new NavPoint($navTitle, NULL, $navId, $navClass, $isNavHidden, $writingDirection); $this->addNavPoint($navPoint); } if ($this->lastLevel !== NULL) { $this->currentLevel = $this->lastLevel; } return $navPoint; } /** * Step back one chapter level. * * Subsequent chapters will be added to this chapters parent level. */ function backLevel() { $this->lastLevel = $this->currentLevel; $this->currentLevel = $this->currentLevel->getParent(); } /** * Step back to the root level. * * Subsequent chapters will be added to the rooot NavMap. */ function rootLevel() { $this->lastLevel = $this->currentLevel; $this->currentLevel = $this->navMap; } /** * Step back to the given level. * Useful for returning to a previous level from deep within the structure. * Values below 2 will have the same effect as rootLevel() * * @param int $newLevel */ function setCurrentLevel($newLevel) { if ($newLevel <= 1) { $this->rootLevel(); } else { while ($this->currentLevel->getLevel() > $newLevel) { $this->backLevel(); } } } /** * Get current level count. * The indentation of the current structure point. * * @return current level count; */ function getCurrentLevel() { return $this->currentLevel->getLevel(); } /** * Add child NavPoints to current level. * * @param NavPoint $navPoint */ function addNavPoint($navPoint) { $this->lastLevel = $this->currentLevel->addNavPoint($navPoint); } /** * * Enter description here ... * * @return NavMap */ function getNavMap() { return $this->navMap; } /** * * Enter description here ... * * @param string $name * @param string $content */ function addMetaEntry($name, $content) { $name = is_string($name) ? trim($name) : NULL; $content = is_string($content) ? trim($content) : NULL; if ($name != NULL && $content != NULL) { $this->meta[] = array($name => $content); } } /** * * Enter description here ... * * @return string */ function finalize() { $nav = $this->navMap->finalize(); $ncx = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; if ($this->isEPubVersion2()) { $ncx .= "<!DOCTYPE ncx PUBLIC \"-//NISO//DTD ncx 2005-1//EN\"\n" . " \"http://www.daisy.org/z3986/2005/ncx-2005-1.dtd\">\n"; } $ncx .= "<ncx xmlns=\"http://www.daisy.org/z3986/2005/ncx/\" version=\"2005-1\" xml:lang=\"" . $this->languageCode . "\" dir=\"" . $this->writingDirection . "\">\n" . "\t<head>\n" . "\t\t<meta name=\"dtb:uid\" content=\"" . $this->uid . "\" />\n" . "\t\t<meta name=\"dtb:depth\" content=\"" . $this->navMap->getNavLevels() . "\" />\n" . "\t\t<meta name=\"dtb:totalPageCount\" content=\"0\" />\n" . "\t\t<meta name=\"dtb:maxPageNumber\" content=\"0\" />\n"; if (sizeof($this->meta)) { foreach ($this->meta as $metaEntry) { list($name, $content) = each($metaEntry); $ncx .= "\t\t<meta name=\"" . $name . "\" content=\"" . $content . "\" />\n"; } } $ncx .= "\t</head>\n\n\t<docTitle>\n\t\t<text>" . $this->docTitle . "</text>\n\t</docTitle>\n\n\t<docAuthor>\n\t\t<text>" . $this->docAuthor . "</text>\n\t</docAuthor>\n\n" . $nav; return $ncx . "</ncx>\n"; } /** * * @param string $title * @param string $cssFileName * @return string */ function finalizeEPub3($title = "Table of Contents", $cssFileName = NULL) { $end = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" . "<html xmlns=\"http://www.w3.org/1999/xhtml\"\n" . " xmlns:epub=\"http://www.idpf.org/2007/ops\"\n" . " xml:lang=\"" . $this->languageCode . "\" lang=\"" . $this->languageCode . "\" dir=\"" . $this->writingDirection . "\">\n" . "\t<head>\n" . "\t\t<title>" . $this->docTitle . "</title>\n" . "\t\t<meta http-equiv=\"default-style\" content=\"text/html; charset=utf-8\"/>\n"; if ($cssFileName !== NULL) { $end .= "\t\t<link rel=\"stylesheet\" href=\"" . $cssFileName . "\" type=\"text/css\"/>\n"; } $end .= "\t</head>\n" . "\t<body epub:type=\"frontmatter toc\">\n" . "\t\t<header>\n" . "\t\t\t<h1>" . $title . "</h1>\n" . "\t\t</header>\n" . $this->navMap->finalizeEPub3() . $this->finalizeEPub3Landmarks() . "\t</body>\n" . "</html>\n"; return $end; } /** * Build the references for the ePub 2 toc. * These are merely reference pages added to the end of the navMap though. * * @return string */ function finalizeReferences() { if (isset($this->referencesList) && sizeof($this->referencesList) > 0) { $this->rootLevel(); $this->subLevel($this->referencesTitle, $this->referencesId, $this->referencesClass); $refId = 1; while (list($item, $descriptive) = each($this->referencesOrder)) { if (array_key_exists($item, $this->referencesList)) { $name = (empty($this->referencesName[$item]) ? $descriptive : $this->referencesName[$item]); $navPoint = new NavPoint($name, $this->referencesList[$item], "ref-" . $refId++); $this->addNavPoint($navPoint); } } } } /** * Build the landmarks for the ePub 3 toc. * @return string */ function finalizeEPub3Landmarks() { $lm = ""; if (isset($this->referencesList) && sizeof($this->referencesList) > 0) { $lm = "\t\t\t<nav epub:type=\"landmarks\">\n" . "\t\t\t\t<h2" . ($this->writingDirection === EPub::DIRECTION_RIGHT_TO_LEFT ? " dir=\"rtl\"" : "") . ">" . $this->referencesTitle . "</h2>\n" . "\t\t\t\t<ol>\n"; $li = ""; while (list($item, $descriptive) = each($this->referencesOrder)) { if (array_key_exists($item, $this->referencesList)) { $li .= "\t\t\t\t\t<li><a epub:type=\"" . $item . "\" href=\"" . $this->referencesList[$item] . "\">" . (empty($this->referencesName[$item]) ? $descriptive : $this->referencesName[$item]) . "</a></li>\n"; } } if (empty($li)) { return ""; } $lm .= $li . "\t\t\t\t</ol>\n" . "\t\t\t</nav>\n"; } return $lm; } } /** * ePub NavMap class */ class NavMap { const _VERSION = 3.00; private $navPoints = array(); private $navLevels = 0; private $writingDirection = NULL; /** * Class constructor. * * @return void */ function __construct($writingDirection = NULL) { $this->setWritingDirection($writingDirection); } /** * Class destructor * * @return void */ function __destruct() { unset($this->navPoints, $this->navLevels, $this->writingDirection); } /** * Set the writing direction to be used for this NavPoint. * * @param string $writingDirection */ function setWritingDirection($writingDirection) { $this->writingDirection = isset($writingDirection) && is_string($writingDirection) ? trim($writingDirection) : NULL; } function getWritingDirection() { return $this->writingDirection; } /** * Add a navPoint to the root of the NavMap. * * @param NavPoint $navPoint * @return NavMap */ function addNavPoint($navPoint) { if ($navPoint != NULL && is_object($navPoint) && get_class($navPoint) === "NavPoint") { $navPoint->setParent($this); if ($navPoint->getWritingDirection() == NULL) { $navPoint->setWritingDirection($this->writingDirection); } $this->navPoints[] = $navPoint; return $navPoint; } return $this; } /** * The final max depth for the "dtb:depth" meta attribute * Only available after finalize have been called. * * @return number */ function getNavLevels() { return $this->navLevels+1; } function getLevel() { return 1; } function getParent() { return $this; } /** * Finalize the navMap, the final max depth for the "dtb:depth" meta attribute can be retrieved with getNavLevels after finalization * */ function finalize() { $playOrder = 0; $this->navLevels = 0; $nav = "\t<navMap>\n"; if (sizeof($this->navPoints) > 0) { $this->navLevels++; foreach ($this->navPoints as $navPoint) { $retLevel = $navPoint->finalize($nav, $playOrder, 0); if ($retLevel > $this->navLevels) { $this->navLevels = $retLevel; } } } return $nav . "\t</navMap>\n"; } /** * Finalize the navMap, the final max depth for the "dtb:depth" meta attribute can be retrieved with getNavLevels after finalization * */ function finalizeEPub3() { $playOrder = 0; $level = 0; $this->navLevels = 0; $nav = "\t\t<nav epub:type=\"toc\" id=\"toc\">\n"; if (sizeof($this->navPoints) > 0) { $this->navLevels++; $nav .= str_repeat("\t", $level) . "\t\t\t<ol epub:type=\"list\">\n"; foreach ($this->navPoints as $navPoint) { $retLevel = $navPoint->finalizeEPub3($nav, $playOrder, 0); if ($retLevel > $this->navLevels) { $this->navLevels = $retLevel; } } $nav .= str_repeat("\t", $level) . "\t\t\t</ol>\n"; } return $nav . "\t\t</nav>\n"; } } /** * ePub NavPoint class */ class NavPoint { const _VERSION = 3.00; private $label = NULL; private $contentSrc = NULL; private $id = NULL; private $navClass = NULL; private $isNavHidden = FALSE; private $navPoints = array(); private $parent = NULL; /** * Class constructor. * * All three attributes are mandatory, though if ID is set to null (default) the value will be generated. * * @param string $label * @param string $contentSrc * @param string $id * @param string $navClass * @param bool $isNavHidden * @param string $writingDirection */ function __construct($label, $contentSrc = NULL, $id = NULL, $navClass = NULL, $isNavHidden = FALSE, $writingDirection = NULL) { $this->setLabel($label); $this->setContentSrc($contentSrc); $this->setId($id); $this->setNavClass($navClass); $this->setNavHidden($isNavHidden); $this->setWritingDirection($writingDirection); } /** * Class destructor * * @return void */ function __destruct() { unset($this->label, $this->contentSrc, $this->id, $this->navClass); unset($this->isNavHidden, $this->navPoints, $this->parent); } /** * Set the Text label for the NavPoint. * * The label is mandatory. * * @param string $label */ function setLabel($label) { $this->label = is_string($label) ? trim($label) : NULL; } /** * Get the Text label for the NavPoint. * * @return string Label */ function getLabel() { return $this->label; } /** * Set the src reference for the NavPoint. * * The src is mandatory for ePub 2. * * @param string $contentSrc */ function setContentSrc($contentSrc) { $this->contentSrc = isset($contentSrc) && is_string($contentSrc) ? trim($contentSrc) : NULL; } /** * Get the src reference for the NavPoint. * * @return string content src url. */ function getContentSrc() { return $this->contentSrc; } /** * Set the parent for this NavPoint. * * @param NavPoint or NavMap $parent */ function setParent($parent) { if ($parent != NULL && is_object($parent) && (get_class($parent) === "NavPoint" || get_class($parent) === "NavMap") ) { $this->parent = $parent; } } /** * Get the parent to this NavPoint. * * @return NavPoint, or NavMap if the parent is the root. */ function getParent() { return $this->parent; } /** * Get the current level. 1 = document root. * * @return int level */ function getLevel() { return $this->parent === NULL ? 1 : $this->parent->getLevel()+1; } /** * Set the id for the NavPoint. * * The id must be unique, and is mandatory. * * @param string $id */ function setId($id) { $this->id = is_string($id) ? trim($id) : NULL; } /** * Set the class to be used for this NavPoint. * * @param string $navClass */ function setNavClass($navClass) { $this->navClass = isset($navClass) && is_string($navClass) ? trim($navClass) : NULL; } /** * Set the class to be used for this NavPoint. * * @param string $navClass */ function setNavHidden($isNavHidden) { $this->isNavHidden = $isNavHidden === TRUE; } /** * Set the writing direction to be used for this NavPoint. * * @param string $writingDirection */ function setWritingDirection($writingDirection) { $this->writingDirection = isset($writingDirection) && is_string($writingDirection) ? trim($writingDirection) : NULL; } function getWritingDirection() { return $this->writingDirection; } /** * Add child NavPoints for multi level NavMaps. * * @param NavPoint $navPoint */ function addNavPoint($navPoint) { if ($navPoint != NULL && is_object($navPoint) && get_class($navPoint) === "NavPoint") { $navPoint->setParent($this); if ($navPoint->getWritingDirection() == NULL) { $navPoint->setWritingDirection($this->writingDirection); } $this->navPoints[] = $navPoint; return $navPoint; } return $this; } /** * * Enter description here ... * * @param string $nav * @param int $playOrder * @param int $level * @return int */ function finalize(&$nav = "", &$playOrder = 0, $level = 0) { $maxLevel = $level; $levelAdjust = 0; if ($this->isNavHidden) { return $maxLevel; } if (isset($this->contentSrc)) { $playOrder++; if ($this->id == NULL) { $this->id = "navpoint-" . $playOrder; } $nav .= str_repeat("\t", $level) . "\t\t<navPoint id=\"" . $this->id . "\" playOrder=\"" . $playOrder . "\">\n" . str_repeat("\t", $level) . "\t\t\t<navLabel>\n" . str_repeat("\t", $level) . "\t\t\t\t<text>" . $this->label . "</text>\n" . str_repeat("\t", $level) . "\t\t\t</navLabel>\n" . str_repeat("\t", $level) . "\t\t\t<content src=\"" . $this->contentSrc . "\" />\n"; } else { $levelAdjust++; } if (sizeof($this->navPoints) > 0) { $maxLevel++; foreach ($this->navPoints as $navPoint) { $retLevel = $navPoint->finalize($nav, $playOrder, ($level+1+$levelAdjust)); if ($retLevel > $maxLevel) { $maxLevel = $retLevel; } } } if (isset($this->contentSrc)) { $nav .= str_repeat("\t", $level) . "\t\t</navPoint>\n"; } return $maxLevel; } /** * * Enter description here ... * * @param string $nav * @param int $playOrder * @param int $level * @return int */ function finalizeEPub3(&$nav = "", &$playOrder = 0, $level = 0, $subLevelClass = NULL, $subLevelHidden = FALSE) { $maxLevel = $level; if ($this->id == NULL) { $this->id = "navpoint-" . $playOrder; } $indent = str_repeat("\t", $level) . "\t\t\t\t"; $nav .= $indent . "<li id=\"" . $this->id . "\""; if (isset($this->writingDirection)) { $nav .= " dir=\"" . $this->writingDirection . "\""; } $nav .= ">\n"; if (isset($this->contentSrc)) { $nav .= $indent . "\t<a href=\"" . $this->contentSrc . "\">" . $this->label . "</a>\n"; } else { $nav .= $indent . "\t<span>" . $this->label . "</span>\n"; } if (sizeof($this->navPoints) > 0) { $maxLevel++; $nav .= $indent . "\t<ol epub:type=\"list\""; if (isset($subLevelClass)) { $nav .= " class=\"" . $subLevelClass . "\""; } if ($subLevelHidden) { $nav .= " hidden=\"hidden\""; } $nav .= ">\n"; foreach ($this->navPoints as $navPoint) { $retLevel = $navPoint->finalizeEPub3($nav, $playOrder, ($level+2), $subLevelClass, $subLevelHidden); if ($retLevel > $maxLevel) { $maxLevel = $retLevel; } } $nav .= $indent . "\t</ol>\n"; } $nav .= $indent . "</li>\n"; return $maxLevel; } } ?>