├── .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) == '".$article.'';
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 .= '| '.($i + 1).'. | '.$entry['title'].' |
';
134 | }
135 | $toc .= '
';
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 .= "| ".$entry['title'].' |
';
94 | }
95 |
96 | return $toc.'
';
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 |
--------------------------------------------------------------------------------