public static function getInstance() { if (IdmlHyperlinkManager::$instance == null) { IdmlHyperlinkManager::$instance = new IdmlHyperlinkManager(); } return IdmlHyperlinkManager::$instance; }
/** * Parse from DOM node. * * @param DOMElement $node */ public function parse(DOMElement $node) { parent::parse($node); $hyperlinkMgr = IdmlHyperlinkManager::getInstance(); // Set the id attribute value $destinationKey = $this->contextualStyle->idmlKeyValues['DestinationUniqueKey']; $this->id = 'Hyperlink_' . $destinationKey; // Save this destination in the hyperlink manager. // After parsing the page completes, its id must be stored for all the hyperlink destinations on the page. $hyperlinkMgr->saveDestinationForPageData($this->id); }
/** The readDesignMap function obtains the filename pointers to the MasterSpreads, Resources, Spreads, * Stories, and XML from the designmap, which is the manifest that contains references to the names and locations * of the package's XML files. * * @return boolean true on success, false on failure. */ public function readDesignMap() { //first read the BackStory.xml file to see if any Stories, Hyperlinks, etc. should be ignored ... $this->readBackingStoryAndSetHiddenIds(); // The designmap contains references to everything in the package $designmap = $this->tempDir . '/designmap.xml'; if (!file_exists($designmap)) { $this->idmlAssembler->getProgressUpdater()->setWarning("{$designmap} does not exist."); return false; } // create DOMDocument and DOMXPath objects $doc = new DomDocument(); $b = $doc->load($designmap); if ($b === false) { return false; } $xpath = new DOMXPath($doc); // Parse the preferences file, which contains the width and height of our pages $tags = $xpath->query('//idPkg:Preferences'); assert($tags->length == 1); $attr = $tags->item(0)->attributes->getNamedItem('src'); $filename = "{$this->tempDir}/{$attr->value}"; $this->preferences = new IdmlPreferences(); $this->preferences->load($filename); IdmlAssembler::updateBookSize($this->preferences->pageWidth, $this->preferences->pageHeight); // The Declaration Manager deals with declared Colors, declared Styles, and declared Style Groups $manager = IdmlDeclarationManager::getInstance(); // Parse the graphics file which contains color declarations (this must be done before parsing the Styles) $tags = $xpath->query('//idPkg:Graphic'); assert($tags->length == 1); $attr = $tags->item(0)->attributes->getNamedItem('src'); $filename = "{$this->tempDir}/{$attr->value}"; $manager->loadDeclaredColors($filename); // There should always be exactly one Styles file *** Version 2: non-PEARSON *** $tags = $xpath->query('//idPkg:Styles'); assert($tags->length == 1); $attr = $tags->item(0)->attributes->getNamedItem('src'); $filename = "{$this->tempDir}/{$attr->value}"; $manager->loadDeclaredStyles($filename); /* // There should always be exactly one Styles file *** Version 1: PEARSON PXE *** $tags = $xpath->query('//idPkg:Styles'); assert( $tags->length == 1 ); $attr = $tags->item(0)->attributes->getNamedItem('src'); $filename = "{$this->tempDir}/{$attr->value}"; $this->style = new IdmlStyles($this); $this->style->filename = $filename; $this->style->load(); */ // Parse fonts file. $tags = $xpath->query('//idPkg:Fonts'); assert($tags->length == 1); $attr = $tags->item(0)->attributes->getNamedItem('src'); $filename = "{$this->tempDir}/{$attr->value}"; IdmlFontManager::getInstance()->loadFonts($filename); // There should always be exactly one Tags file $tags = $xpath->query('//idPkg:Tags'); assert($tags->length == 1); $attr = $tags->item(0)->attributes->getNamedItem('src'); $filename = "{$this->tempDir}/{$attr->value}"; $this->tags = new IdmlTags($this); $this->tags->filename = $filename; $this->tags->load(); // There are possibly several MasterSpread files, which are referenced by real spreads and accessed by their UID's. $tags = $xpath->query('//idPkg:MasterSpread'); foreach ($tags as $tag) { $attr = $tag->attributes->getNamedItem('src'); $filename = "{$this->tempDir}/{$attr->value}"; $matches = null; $found = preg_match('|MasterSpreads/MasterSpread_(.*)\\.xml|', $attr->value, $matches); // u154 <-- MasterSpreads/MasterSpread_u154.xml if ($found == 1) { $UID = $matches[1]; $masterSpread = new IdmlMasterSpread($this); $masterSpread->filename = $filename; $this->masterSpreads[$UID] = $masterSpread; } else { $this->idmlAssembler->getProgressUpdater()->setWarning("Unable to determine UID for MasterSpread '{$attr->value}'."); } } // There are most likely many Spread files, and they are ordered in the designmap // according to the book's page ordering. $tags = $xpath->query('//idPkg:Spread'); foreach ($tags as $tag) { $attr = $tag->attributes->getNamedItem('src'); $filename = "{$this->tempDir}/{$attr->value}"; $spread = new IdmlSpread($this); $spread->filename = $filename; $this->spreads[] = $spread; } // There are most likely many Story files, which are referenced by TextFrames and accessed by a parentStoryUID. $tags = $xpath->query('//idPkg:Story'); foreach ($tags as $tag) { $attr = $tag->attributes->getNamedItem('src'); $filename = "{$this->tempDir}/{$attr->value}"; $matches = null; $found = preg_match('|Stories/Story_(.*)\\.xml|', $attr->value, $matches); // u154 <-- Stories/Story_u154.xml if ($found == 1) { $UID = $matches[1]; if (!array_key_exists($UID, $this->chaucerHidden)) { //create/store the story if it is not hidden by a designer $story = new IdmlStory($UID); $story->filename = $filename; $this->stories[$UID] = $story; } } else { $this->idmlAssembler->getProgressUpdater()->setWarning("Unable to determine UID for Story '{$attr->value}'."); } } //Parse hyperlink information $this->hyperlinks = array(); $tags = $xpath->query('//Hyperlink'); $hyperlinkMgr = IdmlHyperlinkManager::getInstance(); foreach ($tags as $tag) { $properties = IdmlParserHelper::getAllDomNodeAttributesAndProperties($tag); // This code is (we think) PXE processing code if (!array_key_exists($properties['Self'], $this->chaucerHidden)) { // create/store the hyperlink if it is not hidden by a designer // The $destNode can be either a HyperlinkURLDestination or a HyperlinkPageDestination in the IDML, // and is referenced by the Hyperlink element. $destId = $properties['Destination']; $destNode = $xpath->query('//*[@Self="' . $destId . '"]'); if ($destNode->length > 0) { $destNode = $destNode->item(0); $properties['Destination'] = IdmlParserHelper::getAllDomNodeAttributesAndProperties($destNode); $properties['Destination']['DestinationType'] = $destNode->nodeName; } $this->hyperlinks[$properties['Self']] = $properties; } // The remainder of this foreach is specifically written for non-PXE IDML $hyperlinkSource = $properties['Source']; $external = false; // unless the link goes to a page outside the epub // If the Destination property is an array, the destination is a link to either // a page within the epub (with the page UID in the data), or or an external link. // So the destination can be set here. if (is_array($properties['Destination'])) { $anchor = false; // link is to a page, not an anchor if ($properties['Destination']['DestinationType'] == 'HyperlinkURLDestination') { // External page: use as is $hyperlinkDestination = $properties['Destination']['DestinationURL']; $external = true; // This links to a page outside the epub } else { // Internal page. Use the page UID; add the package index and the .html suffix $hyperlinkDestination = $properties['Destination']['DestinationPage']; } } else { $anchor = true; // link is to an anchor on a page. $hyperlinkDestination = 'Hyperlink_' . $properties['DestinationUniqueKey']; } $hyperlinkMgr->setSourceDestination($hyperlinkSource, $hyperlinkDestination, $anchor, $external); } //Parse layer information $layers = $xpath->query('//Layer'); $layerManager = IdmlLayerManager::getInstance(); foreach ($layers as $layer) { $layerManager->addLayer($layer); } return true; }
/** * Returns the text frame UID mapped to the specified page UID, for renaming the page in the href attribute * @param string $pageUID * @return string text frame UID */ public function getDestinationPage($pageUID) { return IdmlHyperlinkManager::getInstance()->getTextFrameUID($pageUID); }
/** * Parse DOM. * * @param DOMDocument $doc */ private function parse(DOMDocument $doc) { $xpath = new DOMXPath($doc); $q = "//idPkg:{$this->xmlRootName}/{$this->xmlRootName}"; $nodes = $xpath->query($q); $pageNode = $nodes->item(0); $attributes = $pageNode->attributes; $this->UID = $attributes->getNamedItem('Self')->value; $itemTransform = $attributes->getNamedItem('ItemTransform')->value; $this->transformation = new IdmlTransformation($itemTransform); $this->pageCount = (int) $attributes->getNamedItem('PageCount')->value; if ($pageNode->hasAttribute('BindingLocation')) { $bindingLocation = (int) $pageNode->getAttribute('BindingLocation'); } else { // this happens with MasterSpreads which do not have a binding location. $bindingLocation = -1; } // First construct the pages (typically one or two pages per spread) // although more than two are technically allowed. // We need to have pages loaded first. $q = "//idPkg:{$this->xmlRootName}/{$this->xmlRootName}//Page"; $nodes = $xpath->query($q); foreach ($nodes as $pageNode) { $newPage = new IdmlPage($this); $newPage->parse($pageNode); $this->pages[] = $newPage; } // Process children text frames and rectangles. $spreadNodes = $doc->getElementsByTagName($this->xmlRootName); $spreadNode = $spreadNodes->item(1); // Child nodes. foreach ($spreadNode->childNodes as $element) { $nodeSelf = ''; if ($element->nodeType == XML_ELEMENT_NODE) { /*@var $element DomNode*/ //get the element's id if possible and make sure it is not to be hidden before we parse it $selfIdNode = $element->attributes->getNamedItem('Self'); if ($selfIdNode) { $nodeSelf = $selfIdNode->nodeValue; } } if ($nodeSelf && array_key_exists($nodeSelf, $this->idmlPackage->chaucerHidden)) { error_log('Processing Spread Page, hiding ' . $element->nodeName . ' ' . $nodeSelf . ' ' . __FILE__ . ' Line ' . __LINE__); } elseif ($element->nodeType == XML_ELEMENT_NODE) { switch ($element->nodeName) { case 'Rectangle': // @TODO - determine whether spans should be used for rectangles parallel to the axes, and code accordingly // $rectangle = new IdmlRectangle(); $rectangle = new IdmlPolygon(); $rectangle->parse($element); $this->dropElementIntoPage($rectangle); break; case 'TextFrame': $newTextFrame = new IdmlTextFrame(); $newTextFrame->parse($element); $this->dropElementIntoPage($newTextFrame); $pageUID = $newTextFrame->page != null ? $newTextFrame->page->UID : '000'; // And now for some hyperlink management: $hyperlinkMgr = IdmlHyperlinkManager::getInstance(); // Update the destination page names for links to anchors. // Refs to the anchors are stored during processing of children, but we don't have the UIDs until the text frame is fully parsed. $hyperlinkMgr->updateDestinationPages($newTextFrame->UID, $pageUID); // Also, map the page UID to the text frame UID so the destination page name can be determined for fixed layout. $hyperlinkMgr->saveTextFrameUID($newTextFrame->UID, $pageUID); break; case 'Group': $group = new IdmlGroup(); $group->parse($element); $this->dropElementIntoPage($group); break; case 'Polygon': $polygon = new IdmlPolygon(); $polygon->parse($element); $this->dropElementIntoPage($polygon); break; case 'Oval': $oval = new IdmlOval(); $oval->parse($element); $this->dropElementIntoPage($oval); break; case 'GraphicLine': $line = new IdmlGraphicLine(); $line->parse($element); $this->dropElementIntoPage($line); break; default: // Placeholder: no default action at this time break; } } } }
/** * Generates the href attribute of the hyperlink * @param IdmlHyperlink $element * @return string $href - href attribute value */ protected function setHyperlinkHref(IdmlHyperlink $element) { // Get the destination data from the hyperlink manager $hyperlinkMgr = IdmlHyperlinkManager::getInstance(); $destinationData = $hyperlinkMgr->getSourceDestination($element->contextualStyle->idmlKeyValues['Self']); // If the anchor is not set, use the destination in the retrieved data. if (!$destinationData['anchor']) { $href = $destinationData['destination']; } else { // $uidArray contains stored page and text frame UIDs; one will be used for the destination filename. $uidArray = $hyperlinkMgr->getDestinationPages($destinationData['destination']); // $destinationUID contains the appropriate id for fixed (page) or reflowable (text frame); // getHyperlinkPage is coded in both fixed and reflowable producers $destinationUID = $this->getHyperlinkPage($uidArray); // Assemble the full href, using both filename and anchor. $href = $destinationUID . '#' . $destinationData['destination']; } return $href; }