├── .gitignore ├── MOBIClass ├── constants.php ├── RecognizeURL.php ├── ContentProvider.php ├── FileInt.php ├── FileDate.php ├── FileByte.php ├── FileTri.php ├── FileShort.php ├── FileRecord.php ├── ImageHandler.php ├── FileElement.php ├── FileString.php ├── Record.php ├── PreprocessedArticle.php ├── Prc.php ├── RecordFactory.php ├── Settings.php ├── OnlineArticle.php ├── EXTHHelper.php ├── downloaders │ └── FanFictionNet.php ├── MultipleFileHandler.php ├── http_build_url.php ├── FileObject.php ├── PalmRecord.php ├── MOBIFile.php ├── Http.php ├── MOBI.php └── CharacterEntities.php ├── LICENSE ├── composer.json ├── README.md └── index.php /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | vendor/* 3 | -------------------------------------------------------------------------------- /MOBIClass/constants.php: -------------------------------------------------------------------------------- 1 | =5.3.0", 21 | "symfony/polyfill-mbstring": "^1.12" 22 | }, 23 | "autoload": { 24 | "files": ["MOBIClass/MOBI.php"] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /MOBIClass/FileInt.php: -------------------------------------------------------------------------------- 1 | set($n); 21 | } 22 | 23 | public function get() 24 | { 25 | return $this->data; 26 | } 27 | 28 | public function set($value) 29 | { 30 | $this->data = intval($value); 31 | } 32 | 33 | public function serialize() 34 | { 35 | return $this->intToString($this->data); 36 | } 37 | 38 | public function unserialize($data) 39 | { 40 | __construct($this->toInt($data)); 41 | } 42 | 43 | public function __toString() 44 | { 45 | return 'FileInt: {'.$this->intAsString($this->data).'}'; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /MOBIClass/FileDate.php: -------------------------------------------------------------------------------- 1 | set($n); 21 | } 22 | 23 | public function get() 24 | { 25 | return $this->data; 26 | } 27 | 28 | public function set($value) 29 | { 30 | $this->data = intval($value); 31 | } 32 | 33 | public function serialize() 34 | { 35 | return $this->intToString($this->data); 36 | } 37 | 38 | public function unserialize($data) 39 | { 40 | __construct($this->toInt($data)); 41 | } 42 | 43 | public function __toString() 44 | { 45 | return 'FileDate: {'.(date('r', $this->data - 94694400)).'}'; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /MOBIClass/FileByte.php: -------------------------------------------------------------------------------- 1 | set($n); 21 | } 22 | 23 | public function get() 24 | { 25 | return $this->data; 26 | } 27 | 28 | public function set($value) 29 | { 30 | $this->data = intval($value) & 0xFF; 31 | } 32 | 33 | public function serialize() 34 | { 35 | return $this->byteToString($this->data); 36 | } 37 | 38 | public function unserialize($data) 39 | { 40 | __construct($this->toInt($data)); 41 | } 42 | 43 | public function __toString() 44 | { 45 | return 'FileByte: {'.$this->byteAsString($this->data).'}'; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /MOBIClass/FileTri.php: -------------------------------------------------------------------------------- 1 | set($n); 21 | } 22 | 23 | public function get() 24 | { 25 | return $this->data; 26 | } 27 | 28 | public function set($value) 29 | { 30 | $this->data = intval($value) & 0xFFFFFF; 31 | } 32 | 33 | public function serialize() 34 | { 35 | return $this->triToString($this->data); 36 | } 37 | 38 | public function unserialize($data) 39 | { 40 | __construct($this->toInt($data)); 41 | } 42 | 43 | public function __toString() 44 | { 45 | return 'FileTri: {'.$this->triAsString($this->data).'}'; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /MOBIClass/FileShort.php: -------------------------------------------------------------------------------- 1 | set($n); 21 | } 22 | 23 | public function get() 24 | { 25 | return $this->data; 26 | } 27 | 28 | public function set($value) 29 | { 30 | $this->data = intval($value) & 0xFFFF; 31 | } 32 | 33 | public function serialize() 34 | { 35 | return $this->shortToString($this->data); 36 | } 37 | 38 | public function unserialize($data) 39 | { 40 | __construct($this->toInt($data)); 41 | } 42 | 43 | public function __toString() 44 | { 45 | return 'FileShort: {'.$this->shortAsString($this->data).'}'; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /MOBIClass/FileRecord.php: -------------------------------------------------------------------------------- 1 | record = $record; 23 | } 24 | 25 | public function getByteLength() 26 | { 27 | return $this->getLength(); 28 | } 29 | 30 | public function getLength() 31 | { 32 | return $this->record->getLength(); 33 | } 34 | 35 | public function get() 36 | { 37 | return $this->record; 38 | } 39 | 40 | public function set($record) 41 | { 42 | $this->record = $record; 43 | } 44 | 45 | public function serialize() 46 | { 47 | return $this->record->serialize(); 48 | } 49 | 50 | public function unserialize($data) 51 | { 52 | __construct($this->record->unserialize($data)); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /MOBIClass/ImageHandler.php: -------------------------------------------------------------------------------- 1 | elements = $elements; 23 | } 24 | 25 | public function getByteLength() 26 | { 27 | return $this->getLength(); 28 | } 29 | 30 | public function getLength() 31 | { 32 | $total = 0; 33 | foreach ($this->elements as $val) { 34 | $total += $val->getByteLength(); 35 | } 36 | 37 | return $total; 38 | } 39 | 40 | public function offsetToEntry($name) 41 | { 42 | $pos = 0; 43 | foreach ($this->elements as $key => $value) { 44 | if ($name == $key) { 45 | break; 46 | } 47 | $pos += $value->getByteLength(); 48 | } 49 | 50 | return $pos; 51 | } 52 | 53 | public function exists($key) 54 | { 55 | return isset($this->elements[$key]); 56 | } 57 | /** 58 | * @param string $key 59 | * 60 | * @return FileObject 61 | */ 62 | public function get($key) 63 | { 64 | return $this->elements[$key]; 65 | } 66 | 67 | /** 68 | * @param string $key 69 | * @param FileObject $value 70 | */ 71 | public function set($key, $value) 72 | { 73 | $this->elements[$key] = $value; 74 | } 75 | 76 | public function add($key, $value) 77 | { 78 | $this->elements[$key] = $value; 79 | } 80 | 81 | public function serialize() 82 | { 83 | $result = ''; 84 | foreach ($this->elements as $val) { 85 | $result .= $val->serialize(); 86 | } 87 | 88 | return $result; 89 | } 90 | 91 | public function unserialize($data) 92 | { 93 | //TODO: If reading is needed -> way more complex 94 | } 95 | 96 | public function __toString() 97 | { 98 | $output = 'FileElement ('.$this->getByteLength()." bytes): {\n"; 99 | foreach ($this->elements as $key => $value) { 100 | $output .= "\t".$key.': '.$value."\n"; 101 | } 102 | $output .= '}'; 103 | 104 | return $output; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /MOBIClass/FileString.php: -------------------------------------------------------------------------------- 1 | forcedLength = -1; 24 | $this->data = ''; 25 | 26 | if ($second != null) { 27 | $this->data = $first; 28 | $this->forcedLength = $second; 29 | } elseif ($first != null) { 30 | if (is_string($first)) { 31 | $this->data = $first; 32 | } else { 33 | $this->forcedLength = $first; 34 | } 35 | } 36 | } 37 | 38 | public function getByteLength() 39 | { 40 | return $this->getLength(); 41 | } 42 | 43 | public function getLength() 44 | { 45 | if ($this->forcedLength >= 0) { 46 | return $this->forcedLength; 47 | } 48 | 49 | return strlen($this->data); 50 | } 51 | 52 | public function get() 53 | { 54 | return $this->data; 55 | } 56 | 57 | public function set($value) 58 | { 59 | $this->data = $value; 60 | } 61 | 62 | public function serialize() 63 | { 64 | $output = $this->data; 65 | $curLength = strlen($output); 66 | 67 | if ($this->forcedLength >= 0) { 68 | if ($this->forcedLength > $curLength) { 69 | return str_pad($output, $this->forcedLength, "\0", STR_PAD_RIGHT); 70 | } elseif ($this->forcedLength == $curLength) { 71 | return $output; 72 | } else { 73 | return substr($output, 0, $this->forcedLength); 74 | } 75 | } 76 | 77 | return $output; 78 | } 79 | 80 | public function unserialize($data) 81 | { 82 | __construct($data); 83 | } 84 | 85 | public function __toString() 86 | { 87 | $out = 'FileString'; 88 | if ($this->forcedLength >= 0) { 89 | $out .= ' '.$this->forcedLength; 90 | } 91 | $out .= ': {"'.str_replace(array(' ', "\0"), ' ', $this->serialize()).'"}'; 92 | 93 | return $out; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /MOBIClass/Record.php: -------------------------------------------------------------------------------- 1 | data = $data; 33 | if ($length >= 0) { 34 | $this->length = $length; 35 | } else { 36 | $this->length = strlen($data); 37 | } 38 | } 39 | 40 | public function compress($compression_method) 41 | { 42 | switch ($compression_method) { 43 | case NO_COMPRESSION: 44 | //Finished! 45 | break; 46 | case PALMDOC_COMPRESSION: 47 | throw new Exception('Not implemented yet'); 48 | break; 49 | case HUFF: 50 | throw new Exception('Not implemented yet'); 51 | break; 52 | default: 53 | throw new Exception('Invalid argument'); 54 | } 55 | } 56 | 57 | public function getByteLength() 58 | { 59 | return $this->getLength(); 60 | } 61 | 62 | /** 63 | * Get the length of the record. 64 | * 65 | * @return int Length of the data 66 | */ 67 | public function getLength() 68 | { 69 | return $this->length; 70 | } 71 | 72 | /** 73 | * Get the data contained in the record. 74 | * 75 | * @return string Data contained in the record 76 | */ 77 | public function get() 78 | { 79 | return $this->data; 80 | } 81 | 82 | /** 83 | * Set the data contained in the record. 84 | * 85 | * @param string $value Data contained in the record 86 | */ 87 | public function set($value) 88 | { 89 | $this->data = $value; 90 | } 91 | 92 | public function serialize() 93 | { 94 | return $this->data; 95 | } 96 | public function unserialize($data) 97 | { 98 | __construct($data); 99 | } 100 | 101 | public function __toString() 102 | { 103 | $toShow = $this->data; 104 | if (strlen($this->data) > 103) { 105 | $toShow = substr($this->data, 0, 100).'...'; 106 | } 107 | $out = "Record: {\n"; 108 | $out .= "\t".htmlspecialchars($toShow)."\n"; 109 | $out .= '}'; 110 | 111 | return $out; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /MOBIClass/PreprocessedArticle.php: -------------------------------------------------------------------------------- 1 | text = $textData; 18 | $this->metadata = $metadata; 19 | 20 | $this->images = $this->downloadImages($imageLinks); 21 | } 22 | 23 | /** 24 | * Create a Preprocessed article from a json string. 25 | * 26 | * @param string $json JSON data. Should be of the following format: 27 | * {"text": "TEXT", "images: ["imageURL1", "imageURL2"], "metadata": {"key": "value"}} 28 | * 29 | * Note: Any image tags should have the recindex attribute set to the appropriate index (the 30 | * same index as the image in the array) 31 | * 32 | * @return PreprocessedArticle The generated preprocessed array 33 | */ 34 | public static function CreateFromJson($json) 35 | { 36 | $data = json_decode($json); 37 | 38 | return new self($data['text'], $data['images'], $data['metadata']); 39 | } 40 | 41 | /** 42 | * Get the text data to be integrated in the MOBI file. 43 | * 44 | * @return string 45 | */ 46 | public function getTextData() 47 | { 48 | return $this->text; 49 | } 50 | /** 51 | * Get the images (an array containing the jpeg data). Array entry 0 will 52 | * correspond to image record 0. 53 | * 54 | * @return array 55 | */ 56 | public function getImages() 57 | { 58 | return $this->images; 59 | } 60 | /** 61 | * Get the metadata in the form of a hashtable (for example, title or author). 62 | * 63 | * @return array 64 | */ 65 | public function getMetaData() 66 | { 67 | return $this->metadata; 68 | } 69 | /** 70 | * @param DOMElement $dom 71 | * 72 | * @return array 73 | */ 74 | private function downloadImages($links) 75 | { 76 | $images = array(); 77 | foreach ($links as $link) { 78 | $imgFile = @imagecreatefromstring(Http::Request($link)); 79 | 80 | if ($imgFile === false) { 81 | $imgFile = @imagecreate(1, 1); 82 | $black = @imagecolorallocate($imgFile, 255, 255, 255); 83 | } 84 | if ($imgFile !== false) { 85 | @imagefilter($imgFile, IMG_FILTER_GRAYSCALE); 86 | 87 | ob_start(); 88 | @imagejpeg($imgFile); 89 | $image = ob_get_contents(); 90 | ob_end_clean(); 91 | 92 | $images[$this->imgCounter] = new FileRecord(new Record($image)); 93 | imagedestroy($imgFile); 94 | 95 | ++$this->imgCounter; 96 | } 97 | } 98 | 99 | return $images; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /MOBIClass/Prc.php: -------------------------------------------------------------------------------- 1 | new FileString(32), 14 | 'attributes' => new FileShort(), 15 | 'version' => new FileShort(), 16 | 'creationTime' => new FileDate(), 17 | 'modificationTime' => new FileDate(), 18 | 'backupTime' => new FileDate(), 19 | 'modificationNumber' => new FileInt(), 20 | 'appInfoID' => new FileInt(), 21 | 'sortInfoID' => new FileInt(), 22 | 'prcType' => new FileString(4), 23 | 'creator' => new FileString(4), 24 | 'uniqueIDSeed' => new FileInt(), 25 | 'nextRecordListID' => new FileInt(), 26 | 'numberRecords' => new FileShort(), 27 | 'recordList' => new FileElement(), 28 | 'filler' => new FileShort(), 29 | 'records' => new FileElement(), 30 | )); 31 | 32 | //Set values from the info block 33 | foreach ($this->elements as $name => $val) { 34 | if ($settings->exists($name)) { 35 | $this->get($name)->set($settings->get($name)); 36 | } 37 | } 38 | 39 | $this->get('numberRecords')->set(sizeof($records)); 40 | 41 | $i = 0; 42 | foreach ($records as $record) { 43 | $offset = new FileInt(); 44 | $attr = new FileByte(); 45 | $uniqueID = new FileTri($i); 46 | 47 | $this->elements['recordList']->add('Rec'.$i, new FileElement(array( 48 | 'offset' => $offset, 49 | 'attribute' => $attr, 50 | 'uniqueID' => $uniqueID, 51 | ))); 52 | 53 | $this->elements['records']->add('Rec'.$i, $record); 54 | ++$i; 55 | } 56 | 57 | $this->updateOffsets($records); 58 | } 59 | 60 | public function getByteLength() 61 | { 62 | throw new Exception('Test'); 63 | } 64 | 65 | public function updateOffsets($records) 66 | { 67 | $base = $this->offsetToEntry('records'); 68 | 69 | $i = 0; 70 | 71 | foreach ($records as $record) { 72 | $el = $this->elements['recordList']->get('Rec'.$i); 73 | 74 | $local = $this->elements['records']->offsetToEntry('Rec'.$i); 75 | 76 | $el->get('offset')->set($base + $local); 77 | 78 | ++$i; 79 | } 80 | } 81 | 82 | public function save($file) 83 | { 84 | $handle = fopen($file, 'w'); 85 | fwrite($handle, $this->serialize()); 86 | fclose($handle); 87 | } 88 | 89 | public function output() 90 | { 91 | echo $this->serialize(); 92 | } 93 | 94 | public function __toString() 95 | { 96 | $output = 'Prc ('.$this->getByteLength()." bytes): {\n"; 97 | foreach ($this->elements as $key => $value) { 98 | $output .= "\t".$key.': '.$value."\n"; 99 | } 100 | $output .= '}'; 101 | 102 | return $output; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /MOBIClass/RecordFactory.php: -------------------------------------------------------------------------------- 1 | settings = $settings; 26 | } 27 | 28 | /** 29 | * Create records from a data string. 30 | * 31 | * @param string $data 32 | * 33 | * @return array(Record) 34 | */ 35 | public function createRecords($data) 36 | { 37 | $records = array(); 38 | $size = $this->settings->get('recordSize'); 39 | $compression = $this->settings->get('compression'); 40 | 41 | $dataEntries = mb_str_split($data, $size, 'utf-8'); 42 | 43 | for ($i = 0, $len = sizeof($dataEntries); $i < $len; ++$i) { 44 | $records[$i] = new Record($dataEntries[$i]); 45 | $records[$i]->compress($compression); 46 | } 47 | 48 | return $records; 49 | } 50 | 51 | public function createEOFRecord() 52 | { 53 | return new Record(0xe98e0d0a); 54 | } 55 | 56 | public function createFCISRecord($textLength) 57 | { 58 | $r = 'FCIS'; 59 | $r .= $this->asString(20, 4); 60 | $r .= $this->asString(16, 4); 61 | $r .= $this->asString(1, 4); 62 | $r .= $this->asString(0, 4); 63 | $r .= $this->asString($textLength, 4); 64 | $r .= $this->asString(0, 4); 65 | $r .= $this->asString(32, 4); 66 | $r .= $this->asString(8, 4); 67 | $r .= $this->asString(1, 2); 68 | $r .= $this->asString(1, 2); 69 | $r .= $this->asString(0, 4); 70 | 71 | return new Record($r); 72 | } 73 | 74 | public function createFLISRecord() 75 | { 76 | $r = 'FLIS'; 77 | $r .= $this->asString(8, 4); 78 | $r .= $this->asString(65, 2); 79 | $r .= $this->asString(0, 2); 80 | $r .= $this->asString(0, 4); 81 | $r .= $this->asString(-1, 4); 82 | $r .= $this->asString(1, 2); 83 | $r .= $this->asString(3, 2); 84 | $r .= $this->asString(3, 4); 85 | $r .= $this->asString(1, 4); 86 | $r .= $this->asString(-1, 4); 87 | 88 | return new Record($r); 89 | } 90 | 91 | private function asString($int, $size) 92 | { 93 | $out = ''; 94 | for ($i = 0; $i < $size; ++$i) { 95 | if ($i > 0) { 96 | $out = ' '.$out; 97 | } 98 | $byte = dechex($int & 0xFF); 99 | if (strlen($byte) == 1) { 100 | $byte = '0'.$byte; 101 | } 102 | $out = $byte.$out; 103 | $int = $int >> 8; 104 | } 105 | 106 | return $out; 107 | } 108 | 109 | public function __toString() 110 | { 111 | $out = "Record Factory: {\n"; 112 | $out .= "\tRecord Size: ".$this->settings->get('recordSize')."\n"; 113 | $out .= "\tCompression: ".$this->settings->get('compression')."\n"; 114 | $out .= '}'; 115 | 116 | return $out; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /MOBIClass/Settings.php: -------------------------------------------------------------------------------- 1 | values = array( 28 | 'attributes' => 0, 29 | 'version' => 0, 30 | 'creationTime' => time() + 94694400, 31 | 'modificationTime' => time() + 94694400, 32 | 'backupTime' => 0, 33 | 'modificationNumber' => 0, 34 | 'appInfoID' => 0, 35 | 'sortInfoID' => 0, 36 | 'prcType' => 'BOOK', 37 | 'creator' => 'MOBI', 38 | 'uniqueIDSeed' => rand(), 39 | 'nextRecordListID' => 0, 40 | 'recordAttributes' => 0, 41 | 'compression' => NO_COMPRESSION, 42 | 'recordSize' => RECORD_SIZE, 43 | 'encryptionType' => NO_ENCRYPTION, 44 | 'mobiIdentifier' => 'MOBI', 45 | 'mobiHeaderLength' => 0xe8, 46 | 'mobiType' => MOBIPOCKET_BOOK, 47 | 'textEncoding' => UTF8, 48 | 'uniqueID' => rand(), 49 | 'fileVersion' => 6, 50 | 'locale' => 0x09, 51 | 'inputLanguage' => 0, 52 | 'outputLanguage' => 0, 53 | 'minimumVersion' => 6, 54 | 'huffmanRecordOffset' => 0, 55 | 'huffmanRecordCount' => 0, 56 | 'exthFlags' => 0x40, 57 | 'drmOffset' => 0xFFFFFFFF, 58 | 'drmCount' => 0, 59 | 'drmSize' => 0, 60 | 'drmFlags' => 0, 61 | 'extraDataFlags' => 0, 62 | 'exthIdentifier' => 'EXTH', 63 | // These can be changed without any risk 64 | 'title' => 'Unknown title', 65 | 'author' => 'Unknown author', 66 | 'subject' => 'Unknown subject', 67 | ); 68 | 69 | foreach ($additionalSettings as $key => $value) { 70 | $this->values[$key] = $value; 71 | } 72 | } 73 | 74 | /** 75 | * Get a value from the settings. 76 | * 77 | * @param string $key Key of the setting 78 | * 79 | * @return mixed The value of the setting 80 | */ 81 | public function get($key) 82 | { 83 | return $this->values[$key]; 84 | } 85 | 86 | /** 87 | * Checks if a value is set. 88 | * 89 | * @param string $key Key of the setting 90 | * 91 | * @return bool True if the value exists 92 | */ 93 | public function exists($key) 94 | { 95 | return isset($this->values[$key]); 96 | } 97 | 98 | public function __toString() 99 | { 100 | $out = "Settings: {\n"; 101 | foreach ($this->values as $key => $value) { 102 | $out .= "\t".$key.': '.$value."\n"; 103 | } 104 | $out .= '}'; 105 | 106 | return $out; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | phpMobi file generator 2 | ====================== 3 | 4 | phpMobi is a php script that can generate .mobi files from valid html 5 | files. While this was meant as an experiment, this tool works quite 6 | well and can be used to generate mobipocket files from most news articles. 7 | 8 | IMPORTANT: Do NOT use this on a public web server: most of it was coded in 9 | a weekend, with no testing and no special attention to security. Also, as no official 10 | documentation for the MOBI file format is available, there will be some bugs/problems 11 | in the generated files, but it works for relatively simple documents on the Kindle 12 | previewer and the Kindle 3. 13 | 14 | MobiPocket is an eBook format created by Mobipocket SA. This tool also 15 | uses a php readability port made by [Keyvan Minoukadeh](http://www.keyvan.net/2010/08/php-readability/). 16 | 17 | Code sample 18 | ------------ 19 | 20 | See index.php for an example of using this program. 21 | 22 | Sending an online article as a download: 23 | 24 | ```php 25 | // Create the MOBI object 26 | $mobi = new MOBI(); 27 | 28 | // Set the content provider 29 | $content = new OnlineArticle('URL'); 30 | $mobi->setContentProvider($content); 31 | 32 | // Get title and make it a 12 character long url-safe filename 33 | $title = $mobi->getTitle(); 34 | if ($title === false) { 35 | $title = 'file'; 36 | } 37 | 38 | $title = urlencode(str_replace(' ', '_', strtolower(substr($title, 0, 12)))); 39 | 40 | // Send the mobi file as download 41 | $mobi->download($title.'.mobi'); 42 | ``` 43 | 44 | Using a previously generated/downloaded html file (will not download any images!): 45 | 46 | ```php 47 | $data = '...'; 48 | $options = array( 49 | 'title' => 'Local document', 50 | 'author' => 'Author name', 51 | 'subject' => 'Subject' 52 | ); 53 | 54 | // Create the MOBI object 55 | $mobi = new MOBI(); 56 | 57 | // Set the data 58 | $mobi->setData($data); 59 | $mobi->setOptions($options); 60 | 61 | // Save the mobi file locally 62 | $mobi->save($options['title'].'.mobi'); 63 | ``` 64 | 65 | Implementation 66 | -------------- 67 | 68 | This code was implemented while reverse-engineering the MobiPocket format. 69 | Therefore this code absolutely isn't optimized for speed, but rather for 70 | easy changes, as getting it to produce valid files was quite fiddly. 71 | 72 | Features 73 | -------- 74 | 75 | Modular content provider system: 76 | Adding a new data source can be done by extending the ContentProvider 77 | class. See the OnlineArticle class for a simple but complete 78 | implementation of such a system. 79 | 80 | Image support: 81 | By default, the online article downloader (and any other content 82 | provider that supports images) will download images and integrate them 83 | into the mobi file. 84 | 85 | Partial UTF-8 support: 86 | In practice UTF-8 just works, but there are some unhandled corner 87 | cases (see missing features). 88 | 89 | Missing Features 90 | ---------------- 91 | 92 | Compression: 93 | This won't be implemented (or if it is, only to serve as a 94 | reference of the format). 95 | 96 | Different eBook types: 97 | MobiPocket supports other formats/layouts, such as newspaper-like 98 | formats. At the moment only the book layout has been implemented. 99 | 100 | Full UTF-8 support: 101 | UTF-8 should work most of the time (it worked every time I 102 | tested it), but there might be some problems when the character 103 | is split over two "records". 104 | 105 | License 106 | ------- 107 | This code is released under the Apache license (version 2.0) 108 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | setContentProvider($content); 29 | 30 | //Get title and make it a 12 character long filename 31 | $title = $mobi->getTitle(); 32 | if ($title === false) { 33 | $title = 'file'; 34 | } 35 | $title = urlencode(str_replace(' ', '_', strtolower(substr($title, 0, 12)))); 36 | 37 | //Send the mobi file as download 38 | $mobi->download($title.'.mobi'); 39 | die; 40 | } else { 41 | //Create the mobi object 42 | $mobi = new MOBI(); 43 | 44 | $content = new MOBIFile(); 45 | 46 | $content->set('title', 'My first eBook'); 47 | $content->set('author', 'Me'); 48 | 49 | $content->appendChapterTitle('Introduction'); 50 | for ($i = 0, $lenI = rand(5, 10); $i < $lenI; ++$i) { 51 | $content->appendParagraph('P'.($i + 1)); 52 | } 53 | 54 | //Based on PHP's imagecreatetruecolor help paage 55 | $im = imagecreatetruecolor(220, 200); 56 | $text_color = imagecolorallocate($im, 233, 14, 91); 57 | imagestring($im, 10, 5, 5, 'A Simple Text String', $text_color); 58 | imagestring($im, 5, 15, 75, 'A Simple Text String', $text_color); 59 | imagestring($im, 3, 25, 125, 'A Simple Text String', $text_color); 60 | imagestring($im, 2, 10, 155, 'A Simple Text String', $text_color); 61 | $content->appendImage($im); 62 | imagedestroy($im); 63 | 64 | $content->appendPageBreak(); 65 | 66 | for ($i = 0, $lenI = rand(10, 15); $i < $lenI; ++$i) { 67 | $content->appendChapterTitle(($i + 1).'. Chapter '.($i + 1)); 68 | 69 | for ($j = 0, $lenJ = rand(20, 40); $j < $lenJ; ++$j) { 70 | $content->appendParagraph('P'.($i + 1).'.'.($j + 1).' TEXT TEXT TEXT'); 71 | } 72 | 73 | $content->appendPageBreak(); 74 | } 75 | 76 | $mobi->setContentProvider($content); 77 | 78 | //Get title and make it a 12 character long filename 79 | $title = $mobi->getTitle(); 80 | if ($title === false) { 81 | $title = 'file'; 82 | } 83 | $title = urlencode(str_replace(' ', '_', strtolower(substr($title, 0, 12)))); 84 | 85 | //Send the mobi file as download 86 | $mobi->download($title.'.mobi'); 87 | die; 88 | } 89 | } 90 | ?> 91 | 92 | 93 | 94 | 95 | Sample 96 | 97 | 98 | Download 99 | 100 | 101 | -------------------------------------------------------------------------------- /MOBIClass/OnlineArticle.php: -------------------------------------------------------------------------------- 1 | init(); 27 | if (!isset($this->metadata['title'])) { 28 | $this->metadata['title'] = CharacterEntities::convert(strip_tags($r->getTitle()->innerHTML)); 29 | } 30 | if (!isset($this->metadata['author'])) { 31 | $parts = parse_url($url); 32 | $this->metadata['author'] = $parts['host']; 33 | } 34 | 35 | $article = $r->getContent()->innerHTML; 36 | if (substr($article, 0, 5) == ''; 38 | } else { 39 | $article = "".$article.''; 40 | } 41 | $doc = new DOMDocument(); 42 | @$doc->loadHTML($article) or die($article); 43 | $doc->normalizeDocument(); 44 | 45 | $this->images = $this->handleImages($doc, $url); 46 | $this->text = $doc->saveHTML(); 47 | } 48 | 49 | /** 50 | * Get the text data to be integrated in the MOBI file. 51 | * 52 | * @return string 53 | */ 54 | public function getTextData() 55 | { 56 | return $this->text; 57 | } 58 | /** 59 | * Get the images (an array containing the jpeg data). Array entry 0 will 60 | * correspond to image record 0. 61 | * 62 | * @return array 63 | */ 64 | public function getImages() 65 | { 66 | return $this->images; 67 | } 68 | /** 69 | * Get the metadata in the form of a hashtable (for example, title or author). 70 | * 71 | * @return array 72 | */ 73 | public function getMetaData() 74 | { 75 | return $this->metadata; 76 | } 77 | /** 78 | * @param DOMElement $dom 79 | * 80 | * @return array 81 | */ 82 | private function handleImages($dom, $url) 83 | { 84 | $images = array(); 85 | 86 | $parts = parse_url($url); 87 | 88 | $savedImages = array(); 89 | 90 | $imgElements = $dom->getElementsByTagName('img'); 91 | foreach ($imgElements as $img) { 92 | $src = $img->getAttribute('src'); 93 | 94 | $is_root = false; 95 | if (substr($src, 0, 1) == '/') { 96 | $is_root = true; 97 | } 98 | 99 | $parsed = parse_url($src); 100 | 101 | if (!isset($parsed['host'])) { 102 | if ($is_root) { 103 | $src = http_build_url($url, $parsed, HTTP_URL_REPLACE); 104 | } else { 105 | $src = http_build_url($url, $parsed, HTTP_URL_JOIN_PATH); 106 | } 107 | } 108 | $img->setAttribute('src', ''); 109 | if (isset($savedImages[$src])) { 110 | $img->setAttribute('recindex', $savedImages[$src]); 111 | } else { 112 | $image = ImageHandler::DownloadImage($src); 113 | 114 | if ($image !== false) { 115 | $images[$this->imgCounter] = new FileRecord(new Record($image)); 116 | 117 | $img->setAttribute('recindex', $this->imgCounter); 118 | $savedImages[$src] = $this->imgCounter; 119 | ++$this->imgCounter; 120 | } 121 | } 122 | } 123 | 124 | return $images; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /MOBIClass/EXTHHelper.php: -------------------------------------------------------------------------------- 1 | > (8 * $i)).$out; 32 | $mask = $mask << 8; 33 | } 34 | 35 | return $out; 36 | } 37 | 38 | public static function getRightRepresentation($type, $value) 39 | { 40 | if ($type >= 100 && $type < 200) { 41 | return $value; 42 | } else { 43 | return self::toHex($value); 44 | } 45 | } 46 | 47 | public static function toHex($value) 48 | { 49 | $out = ''; 50 | for ($i = 0, $len = strlen($value); $i < $len; ++$i) { 51 | if ($i > 0) { 52 | $out .= ' '; 53 | } 54 | $hex = dechex(ord($value[$i])); 55 | if (strlen($hex) < 2) { 56 | $hex = '0'.$hex; 57 | } 58 | $out .= $hex; 59 | } 60 | 61 | return $out; 62 | } 63 | 64 | private static $types = array( 65 | 1 => 'drm server id', 66 | 2 => 'drm commerce id', 67 | 3 => 'drm ebookbase book id', 68 | 100 => 'author', 69 | 101 => 'publisher', 70 | 102 => 'imprint', 71 | 103 => 'description', 72 | 104 => 'isbn', 73 | 105 => 'subject', 74 | 106 => 'publishingdate', 75 | 107 => 'review', 76 | 108 => 'contributor', 77 | 109 => 'rights', 78 | 110 => 'subjectcode', 79 | 111 => 'type', 80 | 112 => 'source', 81 | 113 => 'asin', 82 | 114 => 'versionnumber', 83 | 115 => 'sample', 84 | 116 => 'startreading', 85 | 118 => 'retail price', 86 | 119 => 'retail price currency', 87 | 201 => 'coveroffset', 88 | 202 => 'thumboffset', 89 | 203 => 'hasfakecover', 90 | 204 => 'Creator Software', 91 | 205 => 'Creator Major Version', 92 | 206 => 'Creator Minor Version', 93 | 207 => 'Creator Build Number', 94 | 208 => 'watermark', 95 | 209 => 'tamper proof keys', 96 | 300 => 'fontsignature', 97 | 401 => 'clippinglimit', 98 | 402 => 'publisherlimit', 99 | 403 => '403', 100 | 404 => 'ttsflag', 101 | 501 => 'cdetype', 102 | 502 => 'lastupdatetime', 103 | 503 => 'updatedtitle', 104 | ); 105 | private static $flippedTypes = array( 106 | 'drm server id' => 1, 107 | 'drm commerce id' => 2, 108 | 'drm ebookbase book id' => 3, 109 | 'author' => 100, 110 | 'publisher' => 101, 111 | 'imprint' => 102, 112 | 'description' => 103, 113 | 'isbn' => 104, 114 | 'subject' => 105, 115 | 'publishingdate' => 106, 116 | 'review' => 107, 117 | 'contributor' => 108, 118 | 'rights' => 109, 119 | 'subjectcode' => 110, 120 | 'type' => 111, 121 | 'source' => 112, 122 | 'asin' => 113, 123 | 'versionnumber' => 114, 124 | 'sample' => 115, 125 | 'startreading' => 116, 126 | 'retail price' => 118, 127 | 'retail price currency' => 119, 128 | 'coveroffset' => 201, 129 | 'thumboffset' => 202, 130 | 'hasfakecover' => 203, 131 | 'Creator Software' => 204, 132 | 'Creator Major Version' => 205, 133 | 'Creator Minor Version' => 206, 134 | 'Creator Build Number' => 207, 135 | 'watermark' => 208, 136 | 'tamper proof keys' => 209, 137 | 'fontsignature' => 300, 138 | 'clippinglimit' => 401, 139 | 'publisherlimit' => 402, 140 | '403' => 403, 141 | 'ttsflag' => 404, 142 | 'cdetype' => 501, 143 | 'lastupdatetime' => 502, 144 | 'updatedtitle' => 503, 145 | ); 146 | } 147 | -------------------------------------------------------------------------------- /MOBIClass/downloaders/FanFictionNet.php: -------------------------------------------------------------------------------- 1 | id = intval(substr($ending, 0, strpos($ending, '/'))); 19 | 20 | for ($i = 1; $i <= max(1, $this->chapterCount); ++$i) { 21 | $this->addChapter($i); 22 | } 23 | } 24 | 25 | private function addChapter($n) 26 | { 27 | $doc = new DOMDocument(); 28 | $file = Http::Request(self::$prefix.$this->id.'/'.$n.'/'); 29 | @$doc->loadHTML($file) or die($file); 30 | 31 | if (!$this->downloadedMetadata) { 32 | $this->loadMetadata($doc); 33 | $this->downloadedMetadata = true; 34 | } 35 | if ($this->chapterCount < 0) { 36 | $this->chapterCount = $this->getNumberChapters($doc); 37 | 38 | if ($this->chapterCount > 4) { 39 | die("Too many files to download, don't use php for this!"); 40 | } 41 | } 42 | 43 | $textEl = $doc->getElementById('storytext'); 44 | if ($textEl == null) { 45 | die('Error: '.$doc->saveHTML()); 46 | } 47 | $horizontalRulebars = $doc->getElementsByTagName('hr'); 48 | /* 49 | * @var DOMNode 50 | */ 51 | $hr; 52 | foreach ($horizontalRulebars as $hr) { 53 | $hr->setAttribute('size', null); 54 | $hr->setAttribute('noshade', null); 55 | } 56 | $text = $this->innerHtml($textEl); 57 | 58 | $title = ''; 59 | $selects = $doc->getElementsByTagName('select'); 60 | foreach ($selects as $select) { 61 | if ($select->hasAttribute('name') && $select->getAttribute('name') == 'chapter') { 62 | $options = $select->getElementsByTagName('option'); 63 | 64 | $test = $n.'. '; 65 | foreach ($options as $option) { 66 | $val = $option->nodeValue; 67 | if (substr($val, 0, strlen($test)) == $test) { 68 | $title = substr($val, strlen($test)); 69 | break; 70 | } 71 | } 72 | break; 73 | } 74 | } 75 | $this->addPage($text, $title); 76 | } 77 | 78 | private function getNumberChapters($doc) 79 | { 80 | $selects = $doc->getElementsByTagName('select'); 81 | foreach ($selects as $select) { 82 | if ($select->hasAttribute('name') && $select->getAttribute('name') == 'chapter') { 83 | $options = $select->getElementsByTagName('option'); 84 | 85 | $count = $options->length; 86 | 87 | return $count; 88 | } 89 | } 90 | } 91 | 92 | private function loadMetadata($doc) 93 | { 94 | //Author 95 | $links = $doc->getElementsByTagName('a'); 96 | foreach ($links as $link) { 97 | if ($link == null) { 98 | var_dump($link); 99 | } 100 | if ($link->hasAttribute('href') && substr($link->getAttribute('href'), 0, 3) == '/u/') { 101 | $this->setMetadata('author', $link->nodeValue); 102 | } 103 | } 104 | //Title 105 | /* 106 | $links = $doc->getElementsByTagName('link'); 107 | foreach($links as $link) { 108 | if($link->hasAttribute("rel") && $link->getAttribute("rel") == "canonical"){ 109 | $url = $link->getAttribute("href"); 110 | $title = str_replace("_", " ", substr($url, strrpos($url, "/")+1)); 111 | $this->setMetadata("title", $title); 112 | } 113 | }*/ 114 | 115 | //TODO: Find a more reliable way to extract the title 116 | $title = $doc->getElementsByTagName('b')->item(0)->nodeValue; 117 | $this->setMetadata('title', $title); 118 | } 119 | 120 | private function innerHtml($node) 121 | { 122 | $doc = new DOMDocument(); 123 | foreach ($node->childNodes as $child) { 124 | $doc->appendChild($doc->importNode($child, true)); 125 | } 126 | 127 | return $doc->saveHTML(); 128 | } 129 | 130 | public static function Matches($url) 131 | { 132 | //TODO: Implement with regex 133 | return strpos($url, self::$prefix) !== false; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /MOBIClass/MultipleFileHandler.php: -------------------------------------------------------------------------------- 1 | '.$title.''.$contents.''; 37 | } 38 | $pos = 0; 39 | 40 | if (sizeof($this->toc) > 0) { 41 | $lastToc = $this->toc[sizeof($this->toc) - 1]; 42 | $lastFile = $this->files[sizeof($this->files) - 1]; 43 | $pos = $lastToc['pos'] + strlen($lastFile) + 1; 44 | } 45 | 46 | $this->files[] = $contents; 47 | $this->toc[] = array('title' => $title, 'pos' => $pos); 48 | } 49 | 50 | /** 51 | * Add an image to the file. 52 | * 53 | * @param string $imageContents Data string containing the binary data of the image 54 | * 55 | * @return int The reference of the image 56 | */ 57 | public function addImage($imageContents) 58 | { 59 | $this->images[] = $imageContents; 60 | 61 | return sizeof($this->images) - 1; 62 | } 63 | 64 | /** 65 | * Add an image to the file. 66 | * 67 | * @param string $url Url to the image 68 | * 69 | * @return int The reference of the image, false if the image couldn't be downloaded 70 | */ 71 | public function addImageFromUrl($url) 72 | { 73 | $image = ImageHandler::DownloadImage($url); 74 | 75 | if ($image === false) { 76 | return false; 77 | } 78 | 79 | return $this->addImage($image); 80 | } 81 | 82 | /** 83 | * Set the metadata. 84 | * 85 | * @param string $key Key 86 | * @param string $value Value 87 | */ 88 | public function setMetadata($key, $value) 89 | { 90 | $this->metadata[$key] = $value; 91 | } 92 | 93 | /** 94 | * Get the text data to be integrated in the MOBI file. 95 | * 96 | * @return string 97 | */ 98 | public function getTextData() 99 | { 100 | $data = implode("\n", $this->files); 101 | $begin = ""; 102 | $beforeTOC = $begin.$data; 103 | 104 | $tocPos = strlen($beforeTOC); 105 | 106 | $toc = $this->generateTOC(strlen($begin)); 107 | 108 | $customBegin = ""; 130 | for ($i = 0, $len = sizeof($this->toc); $i < $len; ++$i) { 131 | $entry = $this->toc[$i]; 132 | $position = $entry['pos'] + $base; 133 | $toc .= ''; 134 | } 135 | $toc .= '
'.($i + 1).'.'.$entry['title'].'
'; 136 | 137 | return $toc; 138 | } 139 | /** 140 | * Get the images (an array containing the jpeg data). Array entry 0 will 141 | * correspond to image record 0. 142 | * 143 | * @return array 144 | */ 145 | public function getImages() 146 | { 147 | return $this->images; 148 | } 149 | 150 | /** 151 | * Get the metadata in the form of a hashtable (for example, title or author). 152 | * 153 | * @return array 154 | */ 155 | public function getMetaData() 156 | { 157 | return $this->metadata; 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /MOBIClass/http_build_url.php: -------------------------------------------------------------------------------- 1 | byteLength = $byteLength; 15 | } 16 | 17 | public function getByteLength() 18 | { 19 | if ($this->byteLength >= 0) { 20 | return $this->byteLength; 21 | } 22 | 23 | return $this->getLength(); 24 | } 25 | 26 | public function getLength() 27 | { 28 | throw new Exception("Sub-class needs to implement this if it doesn't have a fixed length"); 29 | } 30 | 31 | /** 32 | * Convert a string to byte format (maximum 4 bytes). 33 | * 34 | * @param string $string Input string 35 | * 36 | * @return int Output integer 37 | */ 38 | public function toInt($string) 39 | { 40 | $out = 0; 41 | for ($i = 0, $len = min(4, strlen($string)); $i < $len; ++$i) { 42 | $out = $out | (ord($string[$i]) << (($len - $i - 1) * 8)); 43 | } 44 | 45 | return $out; 46 | } 47 | 48 | /** 49 | * Convert a byte (stored in an integer) to a string. 50 | * 51 | * @param byte $int 52 | * 53 | * @return string 54 | */ 55 | public function byteToString($int) 56 | { 57 | return $this->toString($int, 1); 58 | } 59 | 60 | /** 61 | * Convert a byte (stored in an integer) to a string. 62 | * 63 | * @param byte $int 64 | * 65 | * @return string 66 | */ 67 | public function byteAsString($int) 68 | { 69 | return $this->asString($int, 1); 70 | } 71 | 72 | /** 73 | * Convert a short (stored in an integer) to a string. 74 | * 75 | * @param short $int 76 | * 77 | * @return string 78 | */ 79 | public function shortToString($int) 80 | { 81 | return $this->toString($int, 2); 82 | } 83 | 84 | /** 85 | * Convert a short (stored in an integer) to a string. 86 | * 87 | * @param short $int 88 | * 89 | * @return string 90 | */ 91 | public function shortAsString($int) 92 | { 93 | return $this->asString($int, 2); 94 | } 95 | 96 | /** 97 | * Convert a tri-byte (stored in an integer) to a string. 98 | * 99 | * @param tri-byte $int 100 | * 101 | * @return string 102 | */ 103 | public function triToString($int) 104 | { 105 | return $this->toString($int, 3); 106 | } 107 | 108 | /** 109 | * Convert a tri-byte (stored in an integer) to a string. 110 | * 111 | * @param tri-byte $int 112 | * 113 | * @return string 114 | */ 115 | public function triAsString($int) 116 | { 117 | return $this->asString($int, 3); 118 | } 119 | 120 | /** 121 | * Convert an integer to a string. 122 | * 123 | * @param int $int 124 | * 125 | * @return string 126 | */ 127 | public function intToString($int) 128 | { 129 | return $this->toString($int, 4); 130 | } 131 | 132 | /** 133 | * Convert an integer to a string. 134 | * 135 | * @param int $int 136 | * 137 | * @return string 138 | */ 139 | public function intAsString($int) 140 | { 141 | return $this->asString($int, 4); 142 | } 143 | 144 | /** 145 | * Convert a number of n bytes to a string. 146 | * 147 | * @param int $int Number that should be converted 148 | * @param int $size Number of bytes to convert 149 | * 150 | * @return string Output string 151 | */ 152 | private function toString($int, $size) 153 | { 154 | $out = ''; 155 | for ($i = 0; $i < $size; ++$i) { 156 | $out = chr($int & 0xFF).$out; 157 | $int = $int >> 8; 158 | } 159 | 160 | return $out; 161 | } 162 | 163 | /** 164 | * Convert a number of n bytes to a string. 165 | * 166 | * @param int $int Number that should be converted 167 | * @param int $size Number of bytes to convert 168 | * 169 | * @return string Output string 170 | */ 171 | private function asString($int, $size) 172 | { 173 | $out = ''; 174 | for ($i = 0; $i < $size; ++$i) { 175 | if ($i > 0) { 176 | $out = ' '.$out; 177 | } 178 | $byte = dechex($int & 0xFF); 179 | if (strlen($byte) == 1) { 180 | $byte = '0'.$byte; 181 | } 182 | $out = $byte.$out; 183 | $int = $int >> 8; 184 | } 185 | 186 | return $out; 187 | } 188 | 189 | /** 190 | * Get the value. 191 | * 192 | * @return mixed Value to get 193 | */ 194 | abstract public function get(); 195 | 196 | /** 197 | * Set the value. 198 | * 199 | * @return mixed Value to set 200 | */ 201 | abstract public function set($value); 202 | 203 | /** 204 | * Serialize the object. 205 | * 206 | * @return string String representation 207 | */ 208 | abstract public function serialize(); 209 | 210 | /** 211 | * Unserialize the object. 212 | * 213 | * @param string $data String representation 214 | */ 215 | abstract public function unserialize($data); 216 | } 217 | -------------------------------------------------------------------------------- /MOBIClass/PalmRecord.php: -------------------------------------------------------------------------------- 1 | elements = new FileElement(array( 18 | 'compression' => new FileShort(), 19 | 'unused' => new FileShort(), 20 | 'textLength' => new FileInt(), 21 | 'recordCount' => new FileShort(), 22 | 'recordSize' => new FileShort(), 23 | 'encryptionType' => new FileShort(), 24 | 'unused2' => new FileShort(), 25 | //MOBI Header 26 | 'mobiIdentifier' => new FileString('MOBI', 4), 27 | 'mobiHeaderLength' => new FileInt(), 28 | 'mobiType' => new FileInt(), 29 | 'textEncoding' => new FileInt(), 30 | 'uniqueID' => new FileInt(), 31 | 'fileVersion' => new FileInt(), 32 | 'reserved' => new FileString(40), 33 | 'firstNonBookIndex' => new FileInt(), 34 | 'fullNameOffset' => new FileInt(), 35 | 'fullNameLength' => new FileInt(), 36 | 'locale' => new FileInt(), 37 | 'inputLanguage' => new FileInt(), 38 | 'outputLanguage' => new FileInt(), 39 | 'minimumVersion' => new FileInt(), 40 | 'firstImageIndex' => new FileInt(), 41 | 'huffmanRecordOffset' => new FileInt(), 42 | 'huffmanRecordCount' => new FileInt(), 43 | 'unused3' => new FileString(8), 44 | 'exthFlags' => new FileInt(0x40), 45 | 'unknown' => new FileString(32), 46 | 'drmOffset' => new FileInt(0xFFFFFFFF), 47 | 'drmCount' => new FileShort(0xFFFFFFFF), 48 | 'drmSize' => new FileShort(), 49 | 'drmFlags' => new FileInt(), 50 | 'mobiFiller' => new FileString(72), 51 | //EXTH Header 52 | 'exthIdentifier' => new FileString('EXTH', 4), 53 | 'exthHeaderLength' => new FileInt(), 54 | 'exthRecordCount' => new FileInt(), 55 | 'exthRecords' => new FileElement(), 56 | 'exthPadding' => new FileString(), 57 | //"fullNamePadding"=>new FileString(100), 58 | 'fullName' => new FileString(), 59 | )); 60 | 61 | //Set values from the info block 62 | foreach ($settings->values as $name => $val) { 63 | //echo $name.", "; 64 | if ($this->elements->exists($name)) { 65 | $this->elements->get($name)->set($settings->get($name)); 66 | } 67 | } 68 | 69 | $els = $settings->values; 70 | 71 | $exthElems = new FileElement(); 72 | $i = 0; 73 | $l = 0; 74 | foreach ($els as $name => $val) { 75 | $type = EXTHHelper::textToType($name); 76 | if ($type !== false) { 77 | $type = new FileInt($type); 78 | $length = new FileInt(8 + strlen($val)); 79 | $data = new FileString($val); 80 | $l += 8 + strlen($val); 81 | $exthElems->add('type'.$i, $type); 82 | $exthElems->add('length'.$i, $length); 83 | $exthElems->add('data'.$i, $data); 84 | ++$i; 85 | } 86 | } 87 | 88 | if ($images > 0) { 89 | $this->elements->get('firstImageIndex')->set($textRecords + 1); 90 | } 91 | $this->elements->get('firstNonBookIndex')->set($textRecords + 2 + $images); 92 | $this->elements->get('reserved')->set(str_pad('', 40, chr(255), STR_PAD_RIGHT)); 93 | $this->elements->get('exthRecordCount')->set($i); 94 | $this->elements->set('exthRecords', $exthElems); 95 | $pad = $l % 4; 96 | $pad = (4 - $pad) % 4; 97 | $this->elements->get('exthPadding')->set(str_pad('', $pad, "\0", STR_PAD_RIGHT)); 98 | $this->elements->get('exthHeaderLength')->set(12 + $l + $pad); 99 | 100 | $this->elements->get('recordCount')->set($textRecords); 101 | 102 | $this->elements->get('fullNameOffset')->set($this->elements->offsetToEntry('fullName')); 103 | $this->elements->get('fullNameLength')->set(strlen($settings->get('title'))); 104 | $this->elements->get('fullName')->set($settings->get('title')); 105 | $this->elements->get('textLength')->set($textLength); 106 | } 107 | 108 | public function getByteLength() 109 | { 110 | return $this->getLength(); 111 | } 112 | 113 | public function getLength() 114 | { 115 | return $this->elements->getByteLength(); 116 | } 117 | 118 | public function get() 119 | { 120 | return $this; 121 | } 122 | 123 | public function set($elements) 124 | { 125 | throw new Exception('Unallowed set'); 126 | } 127 | 128 | public function serialize() 129 | { 130 | return $this->elements->serialize(); 131 | } 132 | 133 | public function unserialize($data) 134 | { 135 | $this->elements->unserialize($data); 136 | } 137 | 138 | public function __toString() 139 | { 140 | $output = 'PalmDoc Record ('.$this->getByteLength()." bytes):\n"; 141 | $output .= $this->elements; 142 | 143 | return $output; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /MOBIClass/MOBIFile.php: -------------------------------------------------------------------------------- 1 | 'Unknown Title', 'toc' => true); 18 | private $parts = array(); 19 | private $images = array(); 20 | 21 | /** 22 | * Get the text data (the "html" code). 23 | */ 24 | public function getTextData() 25 | { 26 | $prefix = ""; 27 | 28 | $title = '

'.$this->settings['title'].'

'; 29 | 30 | list($text, $entries) = $this->generateText(); 31 | 32 | if ($this->settings['toc']) { 33 | $toc = $this->generateTOC($entries); //Generate TOC to get the right length 34 | $toc = $this->generateTOC($entries, strlen($prefix) + strlen($toc) + strlen($title)); //Generate the real TOC 35 | } 36 | 37 | $suffix = ''; 38 | 39 | return $prefix.$toc.$title.$text.$suffix; 40 | } 41 | 42 | /** 43 | * Generate the body's text and the chapter entries. 44 | * 45 | * @return array($string, $entries) $string is the html data, $entries 46 | * contains the level, the title and the position of the titles. 47 | */ 48 | public function generateText() 49 | { 50 | $str = ''; 51 | $entries = array(); 52 | 53 | for ($i = 0; $i < sizeof($this->parts); ++$i) { 54 | list($type, $data) = $this->parts[$i]; 55 | $id = 'title_'.$i; 56 | switch ($type) { 57 | case self::PARAGRAPH: 58 | $str .= '

'.$data.'

'; 59 | break; 60 | case self::PAGEBREAK: 61 | $str .= ''; 62 | break; 63 | case self::H2: 64 | $entries[] = array('level' => 2, 'position' => strlen($str), 'title' => $data, 'id' => $id); 65 | $str .= "

".$data.'

'; 66 | break; 67 | case self::H3: 68 | $entries[] = array('level' => 3, 'position' => strlen($str), 'title' => $data, 'id' => $id); 69 | $str .= "

".$data.'

'; 70 | break; 71 | case self::IMAGE: 72 | $str .= ''; 73 | break; 74 | } 75 | } 76 | 77 | return array($str, $entries); 78 | } 79 | 80 | /** 81 | * Generate a TOC. 82 | * 83 | * @param $entries The entries array generated by generateText 84 | * @param $base The zero position 85 | */ 86 | public function generateTOC($entries, $base = 0) 87 | { 88 | $toc = '

Contents

'; 89 | $toc .= "
"; 90 | for ($i = 0, $len = sizeof($entries); $i < $len; ++$i) { 91 | $entry = $entries[$i]; 92 | $pos = str_pad($entry['position'] + $base, 10, '0', STR_PAD_LEFT); 93 | $toc .= "'; 94 | } 95 | 96 | return $toc.'
".$entry['title'].'
'; 97 | } 98 | 99 | /** 100 | * Get the file records of the images. 101 | */ 102 | public function getImages() 103 | { 104 | return $this->images; 105 | } 106 | 107 | /** 108 | * Get the metadata. 109 | */ 110 | public function getMetaData() 111 | { 112 | return $this->settings; 113 | } 114 | 115 | /** 116 | * Change the file's settings. For example set("author", "John Doe") or set("title", "The adventures of John Doe"). 117 | * 118 | * @param $key Key of the setting to insert. 119 | */ 120 | public function set($key, $value) 121 | { 122 | $this->settings[$key] = $value; 123 | } 124 | 125 | /** 126 | * Get the file's settings. 127 | */ 128 | public function get($key) 129 | { 130 | return $this->settings[$key]; 131 | } 132 | 133 | /** 134 | * Append a paragraph of text to the file. 135 | * 136 | * @param string $text The text to insert. 137 | */ 138 | public function appendParagraph($text) 139 | { 140 | $this->parts[] = array(self::PARAGRAPH, $text); 141 | } 142 | 143 | /** 144 | * Append a chapter title (H2). 145 | * 146 | * @param string $title The title to insert. 147 | */ 148 | public function appendChapterTitle($title) 149 | { 150 | $this->parts[] = array(self::H2, $title); 151 | } 152 | 153 | /** 154 | * Append a section title (H3). 155 | * 156 | * @param string $title The title to insert. 157 | */ 158 | public function appendSectionTitle($title) 159 | { 160 | $this->parts[] = array(self::H3, $title); 161 | } 162 | 163 | public function appendPageBreak() 164 | { 165 | $this->parts[] = array(self::PAGEBREAK, null); 166 | } 167 | 168 | /** 169 | * Append an image. 170 | * 171 | * @param resource $img An image file (for example, created by `imagecreate`) 172 | */ 173 | public function appendImage($img) 174 | { 175 | $imgIndex = sizeof($this->images); 176 | $this->images[] = new FileRecord(new Record(ImageHandler::CreateImage($img))); 177 | $this->parts[] = array(self::IMAGE, $imgIndex); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /MOBIClass/Http.php: -------------------------------------------------------------------------------- 1 | 'val1', 'var2' => 'val2') */ 22 | $postdata = array(), /* HTTP POST Data ie. array('var1' => 'val1', 'var2' => 'val2') */ 23 | $cookie = array(), /* HTTP Cookie Data ie. array('var1' => 'val1', 'var2' => 'val2') */ 24 | $custom_headers = array(), /* Custom HTTP headers ie. array('Referer: http://localhost/ */ 25 | $timeout = 1000, /* Socket timeout in milliseconds */ 26 | $req_hdr = false, /* Include HTTP request headers */ 27 | $res_hdr = false, /* Include HTTP response headers */ 28 | $depth = 4 /* Depth of the iteration left (to avoid redirection loops) */ 29 | ) { 30 | if (self::$cache) { 31 | $cacheFile = 'cache/'.$ip.'/'.str_replace('/', '...', $uri); 32 | 33 | if (is_file($cacheFile)) { 34 | $data = file_get_contents($cacheFile); 35 | 36 | return self::resolveTruncated($data); 37 | } 38 | } 39 | $ret = ''; 40 | $verb = strtoupper($verb); 41 | $cookie_str = ''; 42 | $getdata_str = count($getdata) ? '?' : ''; 43 | $postdata_str = ''; 44 | 45 | foreach ($getdata as $k => $v) { 46 | $getdata_str .= urlencode($k).'='.urlencode($v); 47 | } 48 | 49 | foreach ($postdata as $k => $v) { 50 | $postdata_str .= urlencode($k).'='.urlencode($v).'&'; 51 | } 52 | 53 | foreach ($cookie as $k => $v) { 54 | $cookie_str .= urlencode($k).'='.urlencode($v).'; '; 55 | } 56 | 57 | $crlf = "\r\n"; 58 | $req = $verb.' '.$uri.$getdata_str.' HTTP/1.1'.$crlf; 59 | $req .= 'Host: '.$ip.$crlf; 60 | $req .= 'User-Agent: Mozilla/5.0 Firefox/3.6.12'.$crlf; 61 | $req .= 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'.$crlf; 62 | $req .= 'Accept-Language: en-us,en;q=0.5'.$crlf; 63 | $req .= 'Accept-Encoding: deflate'.$crlf; 64 | $req .= 'Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7'.$crlf; 65 | 66 | foreach ($custom_headers as $k => $v) { 67 | $req .= $k.': '.$v.$crlf; 68 | } 69 | 70 | if (!empty($cookie_str)) { 71 | $req .= 'Cookie: '.substr($cookie_str, 0, -2).$crlf; 72 | } 73 | 74 | if ($verb == 'POST' && !empty($postdata_str)) { 75 | $postdata_str = substr($postdata_str, 0, -1); 76 | $req .= 'Content-Type: application/x-www-form-urlencoded'.$crlf; 77 | $req .= 'Content-Length: '.strlen($postdata_str).$crlf.$crlf; 78 | $req .= $postdata_str; 79 | } else { 80 | $req .= $crlf; 81 | } 82 | 83 | if ($req_hdr) { 84 | $ret .= $req; 85 | } 86 | 87 | if (($fp = @fsockopen($ip, $port, $errno, $errstr)) == false) { 88 | return "Error $errno: $errstr\n"; 89 | } 90 | 91 | stream_set_timeout($fp, 0, $timeout * 1000); 92 | 93 | fputs($fp, $req); 94 | $ret .= stream_get_contents($fp); 95 | fclose($fp); 96 | 97 | $headerSplit = strpos($ret, "\r\n\r\n"); 98 | $header = substr($ret, 0, $headerSplit); 99 | 100 | $redirectURL = self::CheckForRedirect($header); 101 | 102 | if ($redirectURL !== false) { 103 | if ($depth > 0) { 104 | $url_parts = parse_url($redirectURL); 105 | $url_parts['port'] = isset($url_parts['port']) ? $url_parts['port'] : 80; 106 | $url_parts['path'] = isset($url_parts['path']) ? $url_parts['path'] : '/'; 107 | 108 | return self::FullRequest($verb, $url_parts['host'], $url_parts['port'], $url_parts['path'], $getdata, $postdata, $cookie, $custom_headers, $timeout, $req_hdr, $res_hdr, $depth - 1); 109 | } else { 110 | return 'Redirect loop, stopping...'; 111 | } 112 | } 113 | 114 | $truncated = false; 115 | $headerLines = explode("\r\n", $header); 116 | foreach ($headerLines as $line) { 117 | list($name, $value) = explode(':', $line); 118 | $name = trim($name); 119 | $value = trim($value); 120 | 121 | if (strtolower($name) == 'transfer-encoding' && strtolower($value) == 'chunked') { //TODO: Put right values! 122 | $truncated = true; 123 | } 124 | } 125 | 126 | if (!$res_hdr) { 127 | $ret = substr($ret, $headerSplit + 4); 128 | } 129 | 130 | if ($truncated) { 131 | $ret = self::resolveTruncated($ret); 132 | } 133 | if (self::$cache) { 134 | if (!is_dir('cache')) { 135 | mkdir('cache'); 136 | } 137 | if (!is_dir('cache/'.$ip)) { 138 | mkdir('cache/'.$ip); 139 | } 140 | if (!is_file('cache/'.$ip.'/'.str_replace('/', '...', $uri))) { 141 | $h = fopen('cache/'.$ip.'/'.str_replace('/', '...', $uri), 'w'); 142 | fwrite($h, $ret); 143 | fclose($h); 144 | } 145 | } 146 | 147 | return $ret; 148 | } 149 | 150 | private static function resolveTruncated($data) 151 | { 152 | $pos = 0; 153 | $end = strlen($data); 154 | $out = ''; 155 | 156 | while ($pos < $end) { 157 | $endVal = strpos($data, "\r\n", $pos); 158 | $value = hexdec(substr($data, $pos, $endVal - $pos)); 159 | $out .= substr($data, $endVal + 2, $value); 160 | $pos = $endVal + 2 + $value; 161 | } 162 | 163 | return $out; 164 | } 165 | 166 | private static function CheckForRedirect($header) 167 | { 168 | $firstLine = substr($header, 0, strpos($header, "\r\n")); 169 | list($httpVersion, $statusCode, $message) = explode(' ', $firstLine); 170 | 171 | if (substr($statusCode, 0, 1) == '3') { 172 | $part = substr($header, strpos(strtolower($header), 'location: ') + strlen('location: ')); 173 | $location = trim(substr($part, 0, strpos($part, "\r\n"))); 174 | 175 | if (strlen($location) > 0) { 176 | return $location; 177 | } 178 | } 179 | 180 | return false; 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /MOBIClass/MOBI.php: -------------------------------------------------------------------------------- 1 | setInternetSource($url); //Load URL, the result will be cleaned using a Readability port 41 | * $mobi->setFileSource($file); //Load a local file without any extra changes 42 | * $mobi->setData($data); //Load data 43 | * 44 | * //If you want, you can set some optional settings (see Settings.php for all recognized settings) 45 | * $options = array( 46 | * "title"=>"Insert title here", 47 | * "author"=>"Author" 48 | * ); 49 | * $mobi->setOptions($options); 50 | * 51 | * //Then there are two ways to output it: 52 | * $mobi->save($file); //Save the file locally 53 | * $mobi->download($name); //Let the client download the file, make sure the page 54 | * //that calls it doesn't output anything, otherwise it might 55 | * //conflict with the download. $name contains the file name, 56 | * //usually something like "title.mobi" (where the title should 57 | * //be cleaned so as not to contain illegal characters). 58 | * 59 | * 60 | * @author Sander Kromwijk 61 | */ 62 | class MOBI 63 | { 64 | private $source = false; 65 | private $images = array(); 66 | private $optional = array(); 67 | private $imgCounter = 0; 68 | private $debug = false; 69 | private $prc = false; 70 | 71 | public function __construct() 72 | { 73 | } 74 | 75 | public function getTitle() 76 | { 77 | if (isset($this->optional['title'])) { 78 | return $this->optional['title']; 79 | } 80 | 81 | return false; 82 | } 83 | 84 | /** 85 | * Set a content provider as source. 86 | * 87 | * @param ContentProvider $content Content Provider to use 88 | */ 89 | public function setContentProvider($content) 90 | { 91 | $this->setOptions($content->getMetaData()); 92 | $this->setImages($content->getImages()); 93 | $this->setData($content->getTextData()); 94 | } 95 | 96 | /** 97 | * Set a local file as source. 98 | * 99 | * @param string $file Path to the file 100 | */ 101 | public function setFileSource($file) 102 | { 103 | $this->setData(file_get_contents($file)); 104 | } 105 | 106 | /** 107 | * Set the data to use. 108 | * 109 | * @param string $data Data to put in the file 110 | */ 111 | public function setData($data) 112 | { 113 | //$data = utf8_encode($data); 114 | $data = CharacterEntities::convert($data); 115 | //$data = utf8_decode($data); 116 | //$this->source = iconv('UTF-8', 'ISO-8859-1//TRANSLIT', $data); 117 | $this->source = $data; 118 | $this->prc = false; 119 | } 120 | 121 | /** 122 | * Set the images to use. 123 | * 124 | * @param array $data Data to put in the file 125 | */ 126 | public function setImages($data) 127 | { 128 | $this->images = $data; 129 | $this->prc = false; 130 | } 131 | 132 | /** 133 | * Set options, usually for things like titles, authors, etc... 134 | * 135 | * @param array $options Options to set 136 | */ 137 | public function setOptions($options) 138 | { 139 | $this->optional = $options; 140 | $this->prc = false; 141 | } 142 | 143 | /** 144 | * Prepare the prc file. 145 | * 146 | * @return Prc The file that can be used to be saved/downloaded 147 | */ 148 | private function preparePRC() 149 | { 150 | if ($this->source === false) { 151 | throw new Exception('No data set'); 152 | } 153 | if ($this->prc !== false) { 154 | return $this->prc; 155 | } 156 | 157 | $data = $this->source; 158 | $len = strlen($data); 159 | 160 | $settings = new Settings($this->optional); 161 | $rec = new RecordFactory($settings); 162 | $dataRecords = $rec->createRecords($data); 163 | $nRecords = sizeof($dataRecords); 164 | $mobiHeader = new PalmRecord($settings, $dataRecords, $nRecords, $len, sizeof($this->images)); 165 | array_unshift($dataRecords, $mobiHeader); 166 | $dataRecords = array_merge($dataRecords, $this->images); 167 | $dataRecords[] = $rec->createFLISRecord(); 168 | $dataRecords[] = $rec->createFCISRecord($len); 169 | $dataRecords[] = $rec->createEOFRecord(); 170 | $this->prc = new Prc($settings, $dataRecords); 171 | 172 | return $this->prc; 173 | } 174 | 175 | /** 176 | * Save the file locally. 177 | * 178 | * @param string $filename Path to save the file 179 | */ 180 | public function save($filename) 181 | { 182 | $prc = $this->preparePRC(); 183 | $prc->save($filename); 184 | } 185 | 186 | /** 187 | * Let the client download the file. Warning! No data should be 188 | * outputted before or after. 189 | * 190 | * @param string $name Name used for download, usually "title.mobi" 191 | */ 192 | public function download($name) 193 | { 194 | $prc = $this->preparePRC(); 195 | $data = $prc->serialize(); 196 | $length = strlen($data); 197 | 198 | if ($this->debug) { 199 | return; 200 | } //In debug mode, don't start the download 201 | 202 | header('Content-Type: application/x-mobipocket-ebook'); 203 | header('Content-Disposition: attachment; filename="'.$name.'"'); 204 | header('Content-Transfer-Encoding: binary'); 205 | header('Accept-Ranges: bytes'); 206 | header('Cache-control: private'); 207 | header('Pragma: private'); 208 | header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); 209 | header('Content-Length: '.$length); 210 | 211 | echo $data; 212 | //Finished! 213 | } 214 | 215 | /** 216 | * Return the mobi file as string so it can be send manually. 217 | * 218 | * @return string 219 | */ 220 | public function toString() 221 | { 222 | $prc = $this->preparePRC(); 223 | 224 | return $prc->serialize(); 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /MOBIClass/CharacterEntities.php: -------------------------------------------------------------------------------- 1 | output is UTF-8 13 | return $str; 14 | //return utf8_encode($str); 15 | //Convert to CP1252 16 | list($from, $to) = self::generateTables(); 17 | 18 | return str_replace($from, $to, $str); 19 | } 20 | 21 | private static function generateTables() 22 | { 23 | $from = array(); 24 | $to = array(); 25 | 26 | for ($i = 0; $i < 256; ++$i) { 27 | $from[$i] = $to[$i] = chr($i); 28 | } 29 | 30 | $from[0x80] = '€'; 31 | $from[0x82] = '‚'; 32 | $from[0x83] = 'ƒ'; 33 | $from[0x84] = '„'; 34 | $from[0x85] = '…'; 35 | $from[0x86] = '†'; 36 | $from[0x87] = '‡'; 37 | $from[0x88] = 'ˆ'; 38 | $from[0x89] = '‰'; 39 | $from[0x8A] = 'Š'; 40 | $from[0x8B] = '‹'; 41 | $from[0x8C] = 'Œ'; 42 | $from[0x8E] = 'Ž'; 43 | 44 | $from[0x91] = '‘'; 45 | $from[0x92] = '’'; 46 | $from[0x93] = '“'; 47 | $from[0x94] = '”'; 48 | $from[0x95] = '•'; 49 | $from[0x96] = '–'; 50 | $from[0x97] = '—'; 51 | $from[0x98] = '˜'; 52 | $from[0x99] = '™'; 53 | $from[0x9A] = 'š'; 54 | $from[0x9B] = '›'; 55 | $from[0x9C] = 'œ'; 56 | $from[0x9E] = 'ž'; 57 | $from[0x9F] = 'Ÿ'; 58 | 59 | $from[0xA1] = '¡'; 60 | $from[0xA2] = '¢'; 61 | $from[0xA3] = '£'; 62 | $from[0xA4] = '¤'; 63 | $from[0xA5] = '¥'; 64 | $from[0xA6] = '¦'; 65 | $from[0xA7] = '§'; 66 | $from[0xA8] = '¨'; 67 | $from[0xA9] = '©'; 68 | $from[0xAA] = 'ª'; 69 | $from[0xAB] = '«'; 70 | $from[0xAC] = '¬'; 71 | $from[0xAE] = '®'; 72 | $from[0xAF] = '¯'; 73 | 74 | $from[0xB0] = '°'; 75 | $from[0xB1] = '±'; 76 | $from[0xB2] = '²'; 77 | $from[0xB3] = '³'; 78 | $from[0xB4] = '´'; 79 | $from[0xB5] = 'µ'; 80 | $from[0xB6] = '¶'; 81 | $from[0xB7] = '·'; 82 | $from[0xB8] = '¸'; 83 | $from[0xB9] = '¹'; 84 | $from[0xBA] = 'º'; 85 | $from[0xBB] = '»'; 86 | $from[0xBC] = '¼'; 87 | $from[0xBD] = '½'; 88 | $from[0xBE] = '¾'; 89 | $from[0xBF] = '¿'; 90 | 91 | $from[0xC0] = 'À'; 92 | $from[0xC1] = 'Á'; 93 | $from[0xC2] = 'Â'; 94 | $from[0xC3] = 'Ã'; 95 | $from[0xC4] = 'Ä'; 96 | $from[0xC5] = 'Å'; 97 | $from[0xC6] = 'Æ'; 98 | $from[0xC7] = 'Ç'; 99 | $from[0xC8] = 'È'; 100 | $from[0xC9] = 'É'; 101 | $from[0xCA] = 'Ê'; 102 | $from[0xCB] = 'Ë'; 103 | $from[0xCC] = 'Ì'; 104 | $from[0xCD] = 'Í'; 105 | $from[0xCE] = 'Î'; 106 | $from[0xCF] = 'Ï'; 107 | 108 | $from[0xD0] = 'Ð'; 109 | $from[0xD1] = 'Ñ'; 110 | $from[0xD2] = 'Ò'; 111 | $from[0xD3] = 'Ó'; 112 | $from[0xD4] = 'Ô'; 113 | $from[0xD5] = 'Õ'; 114 | $from[0xD6] = 'Ö'; 115 | $from[0xD7] = '×'; 116 | $from[0xD8] = 'Ø'; 117 | $from[0xD9] = 'Ù'; 118 | $from[0xDA] = 'Ú'; 119 | $from[0xDB] = 'Û'; 120 | $from[0xDC] = 'Ü'; 121 | $from[0xDD] = 'Ý'; 122 | $from[0xDE] = 'Þ'; 123 | $from[0xDF] = 'ß'; 124 | 125 | $from[0xE0] = 'à'; 126 | $from[0xE1] = 'á'; 127 | $from[0xE2] = 'â'; 128 | $from[0xE3] = 'ã'; 129 | $from[0xE4] = 'ä'; 130 | $from[0xE5] = 'å'; 131 | $from[0xE6] = 'æ'; 132 | $from[0xE7] = 'ç'; 133 | $from[0xE8] = 'è'; 134 | $from[0xE9] = 'é'; 135 | $from[0xEA] = 'ê'; 136 | $from[0xEB] = 'ë'; 137 | $from[0xEC] = 'ì'; 138 | $from[0xED] = 'í'; 139 | $from[0xEE] = 'î'; 140 | $from[0xEF] = 'ï'; 141 | 142 | $from[0xF0] = 'ð'; 143 | $from[0xF1] = 'ñ'; 144 | $from[0xF2] = 'ò'; 145 | $from[0xF3] = 'ó'; 146 | $from[0xF4] = 'ô'; 147 | $from[0xF5] = 'õ'; 148 | $from[0xF6] = 'ö'; 149 | $from[0xF7] = '÷'; 150 | $from[0xF8] = 'ø'; 151 | $from[0xF9] = 'ù'; 152 | $from[0xFA] = 'ú'; 153 | $from[0xFB] = 'û'; 154 | $from[0xFC] = 'ü'; 155 | $from[0xFD] = 'ý'; 156 | $from[0xFE] = 'þ'; 157 | $from[0xFF] = 'ÿ'; 158 | 159 | return array($from, $to); 160 | } 161 | /* 162 | 00 = U+0000 : NULL 163 | 01 = U+0001 : START OF HEADING 164 | 02 = U+0002 : START OF TEXT 165 | 03 = U+0003 : END OF TEXT 166 | 04 = U+0004 : END OF TRANSMISSION 167 | 05 = U+0005 : ENQUIRY 168 | 06 = U+0006 : ACKNOWLEDGE 169 | 07 = U+0007 : BELL 170 | 08 = U+0008 : BACKSPACE 171 | 09 = U+0009 : HORIZONTAL TABULATION 172 | 0A = U+000A : LINE FEED 173 | 0B = U+000B : VERTICAL TABULATION 174 | 0C = U+000C : FORM FEED 175 | 0D = U+000D : CARRIAGE RETURN 176 | 0E = U+000E : SHIFT OUT 177 | 0F = U+000F : SHIFT IN 178 | 10 = U+0010 : DATA LINK ESCAPE 179 | 11 = U+0011 : DEVICE CONTROL ONE 180 | 12 = U+0012 : DEVICE CONTROL TWO 181 | 13 = U+0013 : DEVICE CONTROL THREE 182 | 14 = U+0014 : DEVICE CONTROL FOUR 183 | 15 = U+0015 : NEGATIVE ACKNOWLEDGE 184 | 16 = U+0016 : SYNCHRONOUS IDLE 185 | 17 = U+0017 : END OF TRANSMISSION BLOCK 186 | 18 = U+0018 : CANCEL 187 | 19 = U+0019 : END OF MEDIUM 188 | 1A = U+001A : SUBSTITUTE 189 | 1B = U+001B : ESCAPE 190 | 1C = U+001C : FILE SEPARATOR 191 | 1D = U+001D : GROUP SEPARATOR 192 | 1E = U+001E : RECORD SEPARATOR 193 | 1F = U+001F : UNIT SEPARATOR 194 | 20 = U+0020 : SPACE 195 | 21 = U+0021 : EXCLAMATION MARK 196 | 22 = U+0022 : QUOTATION MARK 197 | 23 = U+0023 : NUMBER SIGN 198 | 24 = U+0024 : DOLLAR SIGN 199 | 25 = U+0025 : PERCENT SIGN 200 | 26 = U+0026 : AMPERSAND 201 | 27 = U+0027 : APOSTROPHE 202 | 28 = U+0028 : LEFT PARENTHESIS 203 | 29 = U+0029 : RIGHT PARENTHESIS 204 | 2A = U+002A : ASTERISK 205 | 2B = U+002B : PLUS SIGN 206 | 2C = U+002C : COMMA 207 | 2D = U+002D : HYPHEN-MINUS 208 | 2E = U+002E : FULL STOP 209 | 2F = U+002F : SOLIDUS 210 | 30 = U+0030 : DIGIT ZERO 211 | 31 = U+0031 : DIGIT ONE 212 | 32 = U+0032 : DIGIT TWO 213 | 33 = U+0033 : DIGIT THREE 214 | 34 = U+0034 : DIGIT FOUR 215 | 35 = U+0035 : DIGIT FIVE 216 | 36 = U+0036 : DIGIT SIX 217 | 37 = U+0037 : DIGIT SEVEN 218 | 38 = U+0038 : DIGIT EIGHT 219 | 39 = U+0039 : DIGIT NINE 220 | 3A = U+003A : COLON 221 | 3B = U+003B : SEMICOLON 222 | 3C = U+003C : LESS-THAN SIGN 223 | 3D = U+003D : EQUALS SIGN 224 | 3E = U+003E : GREATER-THAN SIGN 225 | 3F = U+003F : QUESTION MARK 226 | 40 = U+0040 : COMMERCIAL AT 227 | 41 = U+0041 : LATIN CAPITAL LETTER A 228 | 42 = U+0042 : LATIN CAPITAL LETTER B 229 | 43 = U+0043 : LATIN CAPITAL LETTER C 230 | 44 = U+0044 : LATIN CAPITAL LETTER D 231 | 45 = U+0045 : LATIN CAPITAL LETTER E 232 | 46 = U+0046 : LATIN CAPITAL LETTER F 233 | 47 = U+0047 : LATIN CAPITAL LETTER G 234 | 48 = U+0048 : LATIN CAPITAL LETTER H 235 | 49 = U+0049 : LATIN CAPITAL LETTER I 236 | 4A = U+004A : LATIN CAPITAL LETTER J 237 | 4B = U+004B : LATIN CAPITAL LETTER K 238 | 4C = U+004C : LATIN CAPITAL LETTER L 239 | 4D = U+004D : LATIN CAPITAL LETTER M 240 | 4E = U+004E : LATIN CAPITAL LETTER N 241 | 4F = U+004F : LATIN CAPITAL LETTER O 242 | 50 = U+0050 : LATIN CAPITAL LETTER P 243 | 51 = U+0051 : LATIN CAPITAL LETTER Q 244 | 52 = U+0052 : LATIN CAPITAL LETTER R 245 | 53 = U+0053 : LATIN CAPITAL LETTER S 246 | 54 = U+0054 : LATIN CAPITAL LETTER T 247 | 55 = U+0055 : LATIN CAPITAL LETTER U 248 | 56 = U+0056 : LATIN CAPITAL LETTER V 249 | 57 = U+0057 : LATIN CAPITAL LETTER W 250 | 58 = U+0058 : LATIN CAPITAL LETTER X 251 | 59 = U+0059 : LATIN CAPITAL LETTER Y 252 | 5A = U+005A : LATIN CAPITAL LETTER Z 253 | 5B = U+005B : LEFT SQUARE BRACKET 254 | 5C = U+005C : REVERSE SOLIDUS 255 | 5D = U+005D : RIGHT SQUARE BRACKET 256 | 5E = U+005E : CIRCUMFLEX ACCENT 257 | 5F = U+005F : LOW LINE 258 | 60 = U+0060 : GRAVE ACCENT 259 | 61 = U+0061 : LATIN SMALL LETTER A 260 | 62 = U+0062 : LATIN SMALL LETTER B 261 | 63 = U+0063 : LATIN SMALL LETTER C 262 | 64 = U+0064 : LATIN SMALL LETTER D 263 | 65 = U+0065 : LATIN SMALL LETTER E 264 | 66 = U+0066 : LATIN SMALL LETTER F 265 | 67 = U+0067 : LATIN SMALL LETTER G 266 | 68 = U+0068 : LATIN SMALL LETTER H 267 | 69 = U+0069 : LATIN SMALL LETTER I 268 | 6A = U+006A : LATIN SMALL LETTER J 269 | 6B = U+006B : LATIN SMALL LETTER K 270 | 6C = U+006C : LATIN SMALL LETTER L 271 | 6D = U+006D : LATIN SMALL LETTER M 272 | 6E = U+006E : LATIN SMALL LETTER N 273 | 6F = U+006F : LATIN SMALL LETTER O 274 | 70 = U+0070 : LATIN SMALL LETTER P 275 | 71 = U+0071 : LATIN SMALL LETTER Q 276 | 72 = U+0072 : LATIN SMALL LETTER R 277 | 73 = U+0073 : LATIN SMALL LETTER S 278 | 74 = U+0074 : LATIN SMALL LETTER T 279 | 75 = U+0075 : LATIN SMALL LETTER U 280 | 76 = U+0076 : LATIN SMALL LETTER V 281 | 77 = U+0077 : LATIN SMALL LETTER W 282 | 78 = U+0078 : LATIN SMALL LETTER X 283 | 79 = U+0079 : LATIN SMALL LETTER Y 284 | 7A = U+007A : LATIN SMALL LETTER Z 285 | 7B = U+007B : LEFT CURLY BRACKET 286 | 7C = U+007C : VERTICAL LINE 287 | 7D = U+007D : RIGHT CURLY BRACKET 288 | 7E = U+007E : TILDE 289 | 7F = U+007F : DELETE 290 | 80 = U+20AC : EURO SIGN 291 | 82 = U+201A : SINGLE LOW-9 QUOTATION MARK 292 | 83 = U+0192 : LATIN SMALL LETTER F WITH HOOK 293 | 84 = U+201E : DOUBLE LOW-9 QUOTATION MARK 294 | 85 = U+2026 : HORIZONTAL ELLIPSIS 295 | 86 = U+2020 : DAGGER 296 | 87 = U+2021 : DOUBLE DAGGER 297 | 88 = U+02C6 : MODIFIER LETTER CIRCUMFLEX ACCENT 298 | 89 = U+2030 : PER MILLE SIGN 299 | 8A = U+0160 : LATIN CAPITAL LETTER S WITH CARON 300 | 8B = U+2039 : SINGLE LEFT-POINTING ANGLE QUOTATION MARK 301 | 8C = U+0152 : LATIN CAPITAL LIGATURE OE 302 | 8E = U+017D : LATIN CAPITAL LETTER Z WITH CARON 303 | 91 = U+2018 : LEFT SINGLE QUOTATION MARK 304 | 92 = U+2019 : RIGHT SINGLE QUOTATION MARK 305 | 93 = U+201C : LEFT DOUBLE QUOTATION MARK 306 | 94 = U+201D : RIGHT DOUBLE QUOTATION MARK 307 | 95 = U+2022 : BULLET 308 | 96 = U+2013 : EN DASH 309 | 97 = U+2014 : EM DASH 310 | 98 = U+02DC : SMALL TILDE 311 | 99 = U+2122 : TRADE MARK SIGN 312 | 9A = U+0161 : LATIN SMALL LETTER S WITH CARON 313 | 9B = U+203A : SINGLE RIGHT-POINTING ANGLE QUOTATION MARK 314 | 9C = U+0153 : LATIN SMALL LIGATURE OE 315 | 9E = U+017E : LATIN SMALL LETTER Z WITH CARON 316 | 9F = U+0178 : LATIN CAPITAL LETTER Y WITH DIAERESIS 317 | A0 = U+00A0 : NO-BREAK SPACE 318 | A1 = U+00A1 : INVERTED EXCLAMATION MARK 319 | A2 = U+00A2 : CENT SIGN 320 | A3 = U+00A3 : POUND SIGN 321 | A4 = U+00A4 : CURRENCY SIGN 322 | A5 = U+00A5 : YEN SIGN 323 | A6 = U+00A6 : BROKEN BAR 324 | A7 = U+00A7 : SECTION SIGN 325 | A8 = U+00A8 : DIAERESIS 326 | A9 = U+00A9 : COPYRIGHT SIGN 327 | AA = U+00AA : FEMININE ORDINAL INDICATOR 328 | AB = U+00AB : LEFT-POINTING DOUBLE ANGLE QUOTATION MARK 329 | AC = U+00AC : NOT SIGN 330 | AD = U+00AD : SOFT HYPHEN 331 | AE = U+00AE : REGISTERED SIGN 332 | AF = U+00AF : MACRON 333 | B0 = U+00B0 : DEGREE SIGN 334 | B1 = U+00B1 : PLUS-MINUS SIGN 335 | B2 = U+00B2 : SUPERSCRIPT TWO 336 | B3 = U+00B3 : SUPERSCRIPT THREE 337 | B4 = U+00B4 : ACUTE ACCENT 338 | B5 = U+00B5 : MICRO SIGN 339 | B6 = U+00B6 : PILCROW SIGN 340 | B7 = U+00B7 : MIDDLE DOT 341 | B8 = U+00B8 : CEDILLA 342 | B9 = U+00B9 : SUPERSCRIPT ONE 343 | BA = U+00BA : MASCULINE ORDINAL INDICATOR 344 | BB = U+00BB : RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK 345 | BC = U+00BC : VULGAR FRACTION ONE QUARTER 346 | BD = U+00BD : VULGAR FRACTION ONE HALF 347 | BE = U+00BE : VULGAR FRACTION THREE QUARTERS 348 | BF = U+00BF : INVERTED QUESTION MARK 349 | C0 = U+00C0 : LATIN CAPITAL LETTER A WITH GRAVE 350 | C1 = U+00C1 : LATIN CAPITAL LETTER A WITH ACUTE 351 | C2 = U+00C2 : LATIN CAPITAL LETTER A WITH CIRCUMFLEX 352 | C3 = U+00C3 : LATIN CAPITAL LETTER A WITH TILDE 353 | C4 = U+00C4 : LATIN CAPITAL LETTER A WITH DIAERESIS 354 | C5 = U+00C5 : LATIN CAPITAL LETTER A WITH RING ABOVE 355 | C6 = U+00C6 : LATIN CAPITAL LETTER AE 356 | C7 = U+00C7 : LATIN CAPITAL LETTER C WITH CEDILLA 357 | C8 = U+00C8 : LATIN CAPITAL LETTER E WITH GRAVE 358 | C9 = U+00C9 : LATIN CAPITAL LETTER E WITH ACUTE 359 | CA = U+00CA : LATIN CAPITAL LETTER E WITH CIRCUMFLEX 360 | CB = U+00CB : LATIN CAPITAL LETTER E WITH DIAERESIS 361 | CC = U+00CC : LATIN CAPITAL LETTER I WITH GRAVE 362 | CD = U+00CD : LATIN CAPITAL LETTER I WITH ACUTE 363 | CE = U+00CE : LATIN CAPITAL LETTER I WITH CIRCUMFLEX 364 | CF = U+00CF : LATIN CAPITAL LETTER I WITH DIAERESIS 365 | D0 = U+00D0 : LATIN CAPITAL LETTER ETH 366 | D1 = U+00D1 : LATIN CAPITAL LETTER N WITH TILDE 367 | D2 = U+00D2 : LATIN CAPITAL LETTER O WITH GRAVE 368 | D3 = U+00D3 : LATIN CAPITAL LETTER O WITH ACUTE 369 | D4 = U+00D4 : LATIN CAPITAL LETTER O WITH CIRCUMFLEX 370 | D5 = U+00D5 : LATIN CAPITAL LETTER O WITH TILDE 371 | D6 = U+00D6 : LATIN CAPITAL LETTER O WITH DIAERESIS 372 | D7 = U+00D7 : MULTIPLICATION SIGN 373 | D8 = U+00D8 : LATIN CAPITAL LETTER O WITH STROKE 374 | D9 = U+00D9 : LATIN CAPITAL LETTER U WITH GRAVE 375 | DA = U+00DA : LATIN CAPITAL LETTER U WITH ACUTE 376 | DB = U+00DB : LATIN CAPITAL LETTER U WITH CIRCUMFLEX 377 | DC = U+00DC : LATIN CAPITAL LETTER U WITH DIAERESIS 378 | DD = U+00DD : LATIN CAPITAL LETTER Y WITH ACUTE 379 | DE = U+00DE : LATIN CAPITAL LETTER THORN 380 | DF = U+00DF : LATIN SMALL LETTER SHARP S 381 | E0 = U+00E0 : LATIN SMALL LETTER A WITH GRAVE 382 | E1 = U+00E1 : LATIN SMALL LETTER A WITH ACUTE 383 | E2 = U+00E2 : LATIN SMALL LETTER A WITH CIRCUMFLEX 384 | E3 = U+00E3 : LATIN SMALL LETTER A WITH TILDE 385 | E4 = U+00E4 : LATIN SMALL LETTER A WITH DIAERESIS 386 | E5 = U+00E5 : LATIN SMALL LETTER A WITH RING ABOVE 387 | E6 = U+00E6 : LATIN SMALL LETTER AE 388 | E7 = U+00E7 : LATIN SMALL LETTER C WITH CEDILLA 389 | E8 = U+00E8 : LATIN SMALL LETTER E WITH GRAVE 390 | E9 = U+00E9 : LATIN SMALL LETTER E WITH ACUTE 391 | EA = U+00EA : LATIN SMALL LETTER E WITH CIRCUMFLEX 392 | EB = U+00EB : LATIN SMALL LETTER E WITH DIAERESIS 393 | EC = U+00EC : LATIN SMALL LETTER I WITH GRAVE 394 | ED = U+00ED : LATIN SMALL LETTER I WITH ACUTE 395 | EE = U+00EE : LATIN SMALL LETTER I WITH CIRCUMFLEX 396 | EF = U+00EF : LATIN SMALL LETTER I WITH DIAERESIS 397 | F0 = U+00F0 : LATIN SMALL LETTER ETH 398 | F1 = U+00F1 : LATIN SMALL LETTER N WITH TILDE 399 | F2 = U+00F2 : LATIN SMALL LETTER O WITH GRAVE 400 | F3 = U+00F3 : LATIN SMALL LETTER O WITH ACUTE 401 | F4 = U+00F4 : LATIN SMALL LETTER O WITH CIRCUMFLEX 402 | F5 = U+00F5 : LATIN SMALL LETTER O WITH TILDE 403 | F6 = U+00F6 : LATIN SMALL LETTER O WITH DIAERESIS 404 | F7 = U+00F7 : DIVISION SIGN 405 | F8 = U+00F8 : LATIN SMALL LETTER O WITH STROKE 406 | F9 = U+00F9 : LATIN SMALL LETTER U WITH GRAVE 407 | FA = U+00FA : LATIN SMALL LETTER U WITH ACUTE 408 | FB = U+00FB : LATIN SMALL LETTER U WITH CIRCUMFLEX 409 | FC = U+00FC : LATIN SMALL LETTER U WITH DIAERESIS 410 | FD = U+00FD : LATIN SMALL LETTER Y WITH ACUTE 411 | FE = U+00FE : LATIN SMALL LETTER THORN 412 | FF = U+00FF : LATIN SMALL LETTER Y WITH DIAERESIS 413 | * 414 | */ 415 | } 416 | --------------------------------------------------------------------------------