3
* Abstract class to allow data exchange between the Horde
4
* applications and various Palm formats.
6
* $Horde: framework/Data/Data/palm.php,v 1.5 2004/06/23 19:43:25 chuck Exp $
10
* @author Mathieu Clabaut <mathieu.clabaut@free.fr>
13
class Data_palm extends Data {
18
* PHP-PDB -- PHP class to write PalmOS databases.
20
* Copyright (C) 2001 - PHP-PDB development team
21
* Licensed under the GNU LGPL software license.
22
* See the doc/LEGAL file for more information
23
* See http://php-pdb.sourceforge.net/ for more information about the library
25
* As a note, storing all of the information as hexadecimal kinda
26
* sucks, but it is tough to store and properly manipulate a binary
27
* string in PHP. We double the size of the data but decrease the
28
* difficulty level immensely.
36
define('PDB_HEADER_SIZE', 72); // Size of the database header
37
define('PDB_INDEX_HEADER_SIZE', 6); // Size of the record index header
38
define('PDB_RECORD_HEADER_SIZE', 8); // Size of the record index entry
39
define('PDB_RESOURCE_SIZE', 10); // Size of the resource index entry
40
define('PDB_EPOCH_1904', 2082844800); // Difference between Palm's time and Unix
43
define('PDB_ATTRIB_RESOURCE', 1);
44
define('PDB_ATTRIB_READ_ONLY', 2);
45
define('PDB_ATTRIB_APPINFO_DIRTY', 4);
46
define('PDB_ATTRIB_BACKUP', 8);
47
define('PDB_ATTRIB_OK_NEWER', 16);
48
define('PDB_ATTRIB_RESET', 32);
49
define('PDB_ATTRIB_OPEN', 64);
50
define('PDB_ATTRIB_LAUNCHABLE', 512);
53
// The first nibble is reserved for the category number
54
// See PDB_CATEGORY_MASK
55
define('PDB_RECORD_ATTRIB_PRIVATE', 16);
56
define('PDB_RECORD_ATTRIB_DELETED', 32);
57
define('PDB_RECORD_ATTRIB_DIRTY', 64);
58
define('PDB_RECORD_ATTRIB_EXPUNGED', 128);
61
define('PDB_CATEGORY_NUM', 16); // Number of categories
62
define('PDB_CATEGORY_NAME_LENGTH', 16); // Bytes allocated for name
63
define('PDB_CATEGORY_SIZE', 276); // 2 + (num * length) + num + 1 + 1
64
define('PDB_CATEGORY_MASK', 15); // Bitmask -- use with attribute of record
65
// to get the category ID
69
define('PDB_DOUBLEMETHOD_UNTESTED', 0);
70
define('PDB_DOUBLEMETHOD_NORMAL', 1);
71
define('PDB_DOUBLEMETHOD_REVERSE', 2);
72
define('PDB_DOUBLEMETHOD_BROKEN', 3);
77
* Contains all of the required methods and variables to write a pdb file.
78
* Extend this class to provide functionality for memos, addresses, etc.
83
var $Records = array(); // All of the data from the records is here
85
var $RecordAttrs = array(); // And their attributes are here
86
var $CurrentRecord = 1; // Which record we are currently editing
87
var $Name = ''; // Name of the PDB file
88
var $TypeID = ''; // The 'Type' of the file (4 chars)
89
var $CreatorID = ''; // The 'Creator' of the file (4 chars)
90
var $Attributes = 0; // Attributes (bitmask)
91
var $Version = 0; // Version of the file
92
var $ModNumber = 0; // Modification number
93
var $CreationTime = 0; // Stored in unix time (Jan 1, 1970)
94
var $ModificationTime = 0; // Stored in unix time (Jan 1, 1970)
95
var $BackupTime = 0; // Stored in unix time (Jan 1, 1970)
96
var $AppInfo = ''; // Basic AppInfo block
97
var $SortInfo = ''; // Basic SortInfo block
98
var $DoubleMethod = PDB_DOUBLEMETHOD_UNTESTED;
99
// What method to use for converting doubles
102
// Creates a new database class
103
function PalmDB($Type = '', $Creator = '', $Name = '') {
104
$this->TypeID = $Type;
105
$this->CreatorID = $Creator;
107
$this->CreationTime = time();
108
$this->ModificationTime = time();
113
* Data manipulation functions
115
* These convert various numbers and strings into the hexadecimal
116
* format that is used internally to construct the file. We use hex
117
* encoded strings since that is a lot easier to work with than binary
118
* data in strings, and we can easily tell how big the true value is.
119
* B64 encoding does some odd stuff, so we just make the memory
120
* consumption grow tremendously and the complexity level drops
124
// Converts a byte and returns the value
125
function Int8($value) {
127
return sprintf("%02x", $value);
131
// Loads a single byte as a number from the file
132
// Use if you want to make your own ReadFile function
133
function LoadInt8($file) {
134
if (is_resource($file))
135
$string = fread($file, 1);
138
return ord($string[0]);
142
// Converts an integer (two bytes) and returns the value
143
function Int16($value) {
145
return sprintf("%02x%02x", $value / 256, $value % 256);
149
// Loads two bytes as a number from the file
150
// Use if you want to make your own ReadFile function
151
function LoadInt16($file) {
152
if (is_resource($file))
153
$string = fread($file, 2);
156
return ord($string[0]) * 256 + ord($string[1]);
160
// Converts an integer (three bytes) and returns the value
161
function Int24($value) {
163
return sprintf("%02x%02x%02x", $value / 65536,
164
($value / 256) % 256, $value % 256);
168
// Loads three bytes as a number from the file
169
// Use if you want to make your own ReadFile function
170
function LoadInt24($file) {
171
if (is_resource($file))
172
$string = fread($file, 3);
175
return ord($string[0]) * 65536 + ord($string[1]) * 256 +
180
// Converts an integer (four bytes) and returns the value
181
// 32-bit integers have problems with PHP when they are bigger than
182
// 0x80000000 (about 2 billion) and that's why I don't use pack() here
183
function Int32($value) {
189
$big = $value / 65536;
190
settype($big, 'integer');
191
$little = $value - ($big * 65536);
193
// Little must contain a value
195
// Big might be zero, and should be 0xFFFF if that is the case.
196
$big = 0xFFFF - $big;
198
$value = PalmDB::Int16($big) . PalmDB::Int16($little);
203
// Loads a four-byte string from a file into a number
204
// Use if you want to make your own ReadFile function
205
function LoadInt32($file) {
206
if (is_resource($file))
207
$string = fread($file, 4);
214
$value += ord($string[$i]);
221
// Converts the number into a double and returns the encoded value
222
// Not sure if this will work on all platforms.
223
// Double(10.53) should return "40250f5c28f5c28f"
224
function Double($value) {
225
if ($this->DoubleMethod == PDB_DOUBLEMETHOD_UNTESTED) {
226
$val = bin2hex(pack('d', 10.53));
227
$val = strtolower($val);
228
if (substr($val, 0, 4) == '8fc2')
229
$this->DoubleMethod = PDB_DOUBLEMETHOD_REVERSE;
230
if (substr($val, 0, 4) == '4025')
231
$this->DoubleMethod = PDB_DOUBLEMETHOD_NORMAL;
232
if ($this->DoubleMethod == PDB_DOUBLEMETHOD_UNTESTED)
233
$this->DoubleMethod = PDB_DOUBLEMETHOD_BROKEN;
236
if ($this->DoubleMethod == PDB_DOUBLEMETHOD_BROKEN)
237
return '0000000000000000';
239
$value = bin2hex(pack('d', $value));
241
if ($this->DoubleMethod == PDB_DOUBLEMETHOD_REVERSE)
242
$value = substr($value, 14, 2) . substr($value, 12, 2) .
243
substr($value, 10, 2) . substr($value, 8, 2) .
244
substr($value, 6, 2) . substr($value, 4, 2) .
245
substr($value, 2, 2) . substr($value, 0, 2);
251
// The reverse? Not coded yet.
252
// Use if you want to make your own ReadFile function
253
function LoadDouble($file) {
254
if (is_resource($file))
255
$string = fread($file, 8);
262
// Converts a string into hexadecimal.
263
// If $maxLen is specified and is greater than zero, the string is
264
// trimmed and will contain up to $maxLen characters.
265
// String("abcd", 2) will return "ab" hex encoded (a total of 4
266
// resulting bytes, but 2 encoded characters).
267
// Returned string is *not* NULL-terminated.
268
function String($value, $maxLen = false) {
269
$value = bin2hex($value);
270
if ($maxLen !== false && $maxLen > 0)
271
$value = substr($value, 0, $maxLen * 2);
276
// Pads a hex-encoded value (typically a string) to a fixed size.
277
// May grow too long if $value starts too long
278
// $value = hex encoded value
279
// $minLen = Append nulls to $value until it reaches $minLen
280
// $minLen is the desired size of the string, unencoded.
281
// PadString('6162', 3) results in '616200' (remember the hex encoding)
282
function PadString($value, $minLen) {
283
$PadBytes = '00000000000000000000000000000000';
284
$PadMe = $minLen - (strlen($value) / 2);
289
return $value . substr($PadBytes, 0, $PadMe * 2);
291
$PadMe = $minLen - (strlen($value) / 2);
299
* Record manipulation functions
302
// Sets the current record pointer to the new record number if an
303
// argument is passed in.
304
// Returns the old record number (just in case you want to jump back)
305
// Does not do basic record initialization if we are going to a new
307
function GoToRecord($num = false) {
309
return $this->CurrentRecord;
310
if (gettype($num) == 'string' && ($num[0] == '+' || $num[0] == '-'))
311
$num = $this->CurrentRecord + $num;
312
$oldRecord = $this->CurrentRecord;
313
$this->CurrentRecord = $num;
318
// Returns the size of the current record if no arguments.
319
// Returns the size of the specified record if arguments.
320
function GetRecordSize($num = false) {
322
$num = $this->CurrentRecord;
323
if (! isset($this->Records[$num]))
325
return strlen($this->Records[$num]) / 2;
329
// Adds to the current record. The input data must be already
330
// hex encoded. Initializes the record if it doesn't exist.
331
function AppendCurrent($value) {
332
if (! isset($this->Records[$this->CurrentRecord]))
333
$this->Records[$this->CurrentRecord] = '';
334
$this->Records[$this->CurrentRecord] .= $value;
338
// Adds a byte to the current record
339
function AppendInt8($value) {
340
$this->AppendCurrent($this->Int8($value));
344
// Adds an integer (2 bytes) to the current record
345
function AppendInt16($value) {
346
$this->AppendCurrent($this->Int16($value));
350
// Adds an integer (4 bytes) to the current record
351
function AppendInt32($value) {
352
$this->AppendCurrent($this->Int32($value));
356
// Adds a double to the current record
357
function AppendDouble($value) {
358
$this->AppendCurrent($this->Double($value));
362
// Adds a string (not NULL-terminated)
363
function AppendString($value, $maxLen = false) {
364
$this->AppendCurrent($this->String($value, $maxLen));
368
// Returns true if the specified/current record exists and is set
369
function RecordExists($Rec = false) {
371
$Rec = $this->CurrentRecord;
372
if (isset($this->Records[$Rec]))
378
// Returns the hex-encoded data for the specified record or the current
379
// record if not specified
380
function GetRecord($Rec = false) {
382
$Rec = $this->CurrentRecord;
383
if (isset($this->Records[$Rec]))
384
return $this->Records[$Rec];
389
// Returns the raw data inside the current/specified record. Use this
390
// for odd record types (like a Datebook record). Also, use this
391
// instead of just using $PDB->Records[] directly.
392
function GetRecordRaw($Rec = false) {
394
$Rec = $this->CurrentRecord;
395
if (isset($this->Records[$Rec]))
396
return $this->Records[$Rec];
401
// Sets the hex-encoded data (or whatever) for the current record
402
// Use this instead of the Append* functions if you have an odd
403
// type of record (like a Datebook record).
404
// Also, use this instead of just setting $PDB->Records[]
406
// SetRecordRaw('data');
407
// SetRecordRaw(24, 'data'); (specifying the record num)
408
function SetRecordRaw($A, $B = false) {
411
$A = $this->CurrentRecord;
413
$this->Records[$A] = $B;
417
// Deletes the current record
418
// You are urged to use GoToRecord() and jump to an existing record
419
// after this function call so that the deleted record doesn't
420
// get accidentally recreated/used -- all append functions will
421
// create a new, empty record if the current record doesn't exist.
422
function DeleteCurrentRecord() {
423
if (isset($this->Records[$this->CurrentRecord]))
424
unset($this->Records[$this->CurrentRecord]);
425
if (isset($this->RecordAttrs[$this->CurrentRecord]))
426
unset($this->RecordAttrs[$this->CurrentRecord]);
430
// Returns an array of available record IDs in the order they should
432
// Probably should only be called within the class.
433
function GetRecordIDs() {
434
$keys = array_keys($this->Records);
435
if (! is_array($keys) || count($keys) < 1)
437
sort($keys, SORT_NUMERIC);
442
// Returns the number of records. This should match the number of
443
// keys returned by GetRecordIDs().
444
function GetRecordCount() {
445
return count($this->Records);
449
// Returns the size of the AppInfo block.
450
// Used only for writing
451
function GetAppInfoSize() {
452
if (! isset($this->AppInfo))
454
return strlen($this->AppInfo) / 2;
458
// Returns the AppInfo block (hex encoded)
459
// Used only for writing
460
function GetAppInfo() {
461
if (! isset($this->AppInfo))
463
return $this->AppInfo;
467
// Returns the size of the SortInfo block
468
// Used only for writing
469
function GetSortInfoSize() {
470
if (! isset($this->SortInfo))
472
return strlen($this->SortInfo) / 2;
476
// Returns the SortInfo block (hex encoded)
477
// Used only for writing
478
function GetSortInfo() {
479
if (! isset($this->SortInfo))
481
return $this->SortInfo;
489
// Creates the hex-encoded data to be stuck in the AppInfo
490
// block if the database supports categories.
493
// $categoryArray[id#] = name
495
// $categoryArray[id#]['name'] = name
496
// $categoryArray[id#]['renamed'] = true / false
499
// * I'd suggest numbering your categories sequentially
500
// * Do not have a category 0. It must always be 'Unfiled'. This
501
// function will overwrite any category with the ID of 0.
502
// * There is a maximum of 16 categories, including 'Unfiled'.
504
// Category 0 is reserved for 'Unfiled'
505
// Categories 1-127 are used for handheld ID numbers
506
// Categories 128-255 are used for desktop ID numbers
507
// Do not let category numbers be created larger than 255
508
function CreateCategoryData($CategoryArray) {
509
$CategoryArray[0] = array('Name' => 'Unfiled', 'Renamed' => false);
515
$keys = array_keys($CategoryArray);
517
foreach ($keys as $id) {
518
if ($CatsWritten < PDB_CATEGORY_NUM) {
520
$LastIdWritten = $id;
522
if (is_array($CategoryArray[$id]) &&
523
isset($CategoryArray[$id]['Renamed']) &&
524
$CategoryArray[$id]['Renamed'])
527
if (is_array($CategoryArray[$id])) {
528
if (isset($CategoryArray[$id]['Name']))
529
$name = $CategoryArray[$id]['Name'];
531
$name = $CategoryArray[$id];
532
$name = $this->String($name, PDB_CATEGORY_NAME_LENGTH);
533
$CategoryStr .= $this->PadString($name,
534
PDB_CATEGORY_NAME_LENGTH);
535
$IdStr .= $this->Int8($id);
539
while ($CatsWritten < PDB_CATEGORY_NUM) {
543
$CategoryStr .= $this->PadString('', PDB_CATEGORY_NAME_LENGTH);
544
$IdStr .= $this->Int8($LastIdWritten);
547
$TrailingBytes = $this->Int8($LastIdWritten);
548
$TrailingBytes .= $this->Int8(0);
551
if ($LastIdWritten >= 256)
552
return $this->PadString('', PDB_CATEGORY_SIZE);
554
return $this->Int16($RenamedFlags) . $CategoryStr . $IdStr .
559
// This should be called by other subclasses that use category support
560
// It returns a category array. Each element in the array is another
561
// array with the key 'name' set to the name of the category and
562
// the key 'renamed' set to the renamed flag for that category.
563
function LoadCategoryData($fileData) {
564
$RenamedFlags = $this->LoadInt16(substr($fileData, 0, 2));
566
$StartingFlag = 65536;
567
$Categories = array();
568
while ($StartingFlag > 1) {
570
$Name = substr($fileData, $Offset, PDB_CATEGORY_NAME_LENGTH);
572
while ($i < PDB_CATEGORY_NAME_LENGTH && $Name[$i] != "\0")
576
elseif ($i < PDB_CATEGORY_NAME_LENGTH)
577
$Name = substr($Name, 0, $i);
578
if ($RenamedFlags & $StartingFlag)
581
$RenamedFlag = false;
582
$Categories[] = array('Name' => $Name, 'Renamed' => $RenamedFlag);
583
$Offset += PDB_CATEGORY_NAME_LENGTH;
586
$CategoriesParsed = array();
588
foreach ($Categories as $CategoryData) {
589
$UID = $this->LoadInt8(substr($fileData, $Offset, 1));
591
if ($CategoryData['Name'] != '')
592
$CategoriesParsed[$UID] = $CategoryData;
595
// Ignore the last ID
596
return $CategoriesParsed;
601
* Database Writing Functions
605
// Takes a hex-encoded string and makes sure that when decoded, the data
606
// lies on a four-byte boundary. If it doesn't, it pads the string with
609
* Commented out because we don't use this function currently.
610
* It is part of a test to see what is needed to get files to sync
611
* properly with Desktop 4.0
613
function PadTo4ByteBoundary($string) {
614
while ((strlen($string)/2) % 4) {
622
// Returns the hex encoded header of the pdb file
623
// Header = name, attributes, version, creation/modification/backup
624
// dates, modification number, some offsets, record offsets,
625
// record attributes, appinfo block, sortinfo block
626
// Shouldn't be called from outside the class
627
function MakeHeader() {
628
// 32 bytes = name, but only 31 available (one for null)
629
$header = $this->String($this->Name, 31);
630
$header = $this->PadString($header, 32);
632
// Attributes & version fields
633
$header .= $this->Int16($this->Attributes);
634
$header .= $this->Int16($this->Version);
636
// Creation, modification, and backup date
637
if ($this->CreationTime != 0)
638
$header .= $this->Int32($this->CreationTime + PDB_EPOCH_1904);
640
$header .= $this->Int32(time() + PDB_EPOCH_1904);
641
if ($this->ModificationTime != 0)
642
$header .= $this->Int32($this->ModificationTime + PDB_EPOCH_1904);
644
$header .= $this->Int32(time() + PDB_EPOCH_1904);
645
if ($this->BackupTime != 0)
646
$header .= $this->Int32($this->BackupTime + PDB_EPOCH_1904);
648
$header .= $this->Int32(0);
650
// Calculate the initial offset
651
$Offset = PDB_HEADER_SIZE + PDB_INDEX_HEADER_SIZE;
652
$Offset += PDB_RECORD_HEADER_SIZE * count($this->GetRecordIDs());
654
// Modification number, app information id, sort information id
655
$header .= $this->Int32($this->ModNumber);
657
$AppInfo_Size = $this->GetAppInfoSize();
658
if ($AppInfo_Size > 0) {
659
$header .= $this->Int32($Offset);
660
$Offset += $AppInfo_Size;
662
$header .= $this->Int32(0);
664
$SortInfo_Size = $this->GetSortInfoSize();
665
if ($SortInfo_Size > 0) {
666
$header .= $this->Int32($Offset);
667
$Offset += $SortInfo_Size;
669
$header .= $this->Int32(0);
672
$header .= $this->String($this->TypeID, 4);
673
$header .= $this->String($this->CreatorID, 4);
676
$header .= $this->Int32(0);
679
$header .= $this->Int32(0);
682
$header .= $this->Int16($this->GetRecordCount());
684
// Compensate for the extra 2 NULL characters in the $Offset
688
if ($this->GetRecordCount() != 0) {
689
$keys = $this->GetRecordIDs();
690
sort($keys, SORT_NUMERIC);
691
foreach ($keys as $index) {
692
$header .= $this->Int32($Offset);
693
if (isset($this->RecordAttrs[$index]))
694
$header .= $this->Int8($this->RecordAttrs[$index]);
696
$header .= $this->Int8(0);
698
// The unique id is just going to be the record number
699
$header .= $this->Int24($index);
701
$Offset += $this->GetRecordSize($index);
703
//$Mod4 = $Offset % 4;
705
// $Offset += 4 - $Mod4;
709
// These are the mysterious two NULL characters that we need
710
$header .= $this->Int16(0);
712
// AppInfo and SortInfo blocks go here
713
if ($AppInfo_Size > 0)
715
$header .= $this->GetAppInfo();
716
//$header .= $this->PadTo4ByteBoundary($this->GetAppInfo());
718
if ($SortInfo_Size > 0)
720
$header .= $this->GetSortInfo();
721
//$header .= $this->PadTo4ByteBoundary($this->GetSortInfo());
727
// Writes the database to the file handle specified.
728
// Use this function like this:
729
// $file = fopen("output.pdb", "wb");
730
// // "wb" = write binary for non-Unix systems
732
// echo "big problem -- can't open file";
735
// $pdb->WriteToFile($file);
737
function WriteToFile($file) {
738
$header = $this->MakeHeader();
739
fwrite($file, pack('H*', $header), strlen($header) / 2);
740
$keys = $this->GetRecordIDs();
741
sort($keys, SORT_NUMERIC);
742
foreach ($keys as $index) {
744
//$data = $this->PadTo4ByteBoundary($this->GetRecord($index));
745
$data = $this->GetRecord($index);
746
fwrite($file, pack('H*', $data), strlen($data) / 2);
752
// Writes the database to the standard output (like echo).
753
// Can be trapped with output buffering
754
function WriteToStdout() {
755
// You'd think these three lines would work.
756
// If someone can figure out why they don't, please tell me.
758
// $fp = fopen('php://stdout', 'wb');
759
// $this->WriteToFile($fp);
762
$header = $this->MakeHeader();
763
echo pack("H*", $header);
764
$keys = $this->GetRecordIDs();
765
sort($keys, SORT_NUMERIC);
766
foreach ($keys as $index) {
768
$data = $this->GetRecord($index);
769
//$data = $this->PadTo4ByteBoundary($this->GetRecord($index));
770
echo pack("H*", $data);
776
* Loading in a database
779
// Reads data from the file and tries to load it properly
780
// $file is the already-opened file handle.
781
// Returns false if no error
782
function ReadFile($file) {
783
// 32 bytes = name, but only 31 available
784
$this->Name = fread($file, 32);
787
while ($i < 32 && $this->Name[$i] != "\0")
789
$this->Name = substr($this->Name, 0, $i);
791
$this->Attributes = $this->LoadInt16($file);
792
$this->Version = $this->LoadInt16($file);
794
$this->CreationTime = $this->LoadInt32($file);
795
if ($this->CreationTime != 0)
796
$this->CreationTime -= PDB_EPOCH_1904;
797
if ($this->CreationTime < 0)
798
$this->CreationTime = 0;
800
$this->ModificationTime = $this->LoadInt32($file);
801
if ($this->ModificationTime != 0)
802
$this->ModificationTime -= PDB_EPOCH_1904;
803
if ($this->ModificationTime < 0)
804
$this->ModificationTime = 0;
806
$this->BackupTime = $this->LoadInt32($file);
807
if ($this->BackupTime != 0)
808
$this->BackupTime -= PDB_EPOCH_1904;
809
if ($this->BackupTime < 0)
810
$this->BackupTime = 0;
812
// Modification number
813
$this->ModNumber = $this->LoadInt32($file);
815
// AppInfo and SortInfo size
816
$AppInfoOffset = $this->LoadInt32($file);
817
$SortInfoOffset = $this->LoadInt32($file);
820
$this->TypeID = fread($file, 4);
821
$this->CreatorID = fread($file, 4);
823
// Skip unique ID seed
826
// skip next record list (hope that's ok)
829
$RecCount = $this->LoadInt16($file);
831
$RecordData = array();
833
while ($RecCount > 0) {
835
$Offset = $this->LoadInt32($file);
836
$Attrs = $this->LoadInt8($file);
837
$UID = $this->LoadInt24($file);
838
$RecordData[] = array('Offset' => $Offset, 'Attrs' => $Attrs,
842
// Create the offset list
843
if ($AppInfoOffset != 0)
844
$OffsetList[$AppInfoOffset] = 'AppInfo';
845
if ($SortInfoOffset != 0)
846
$OffsetList[$SortInfoOffset] = 'SortInfo';
847
foreach ($RecordData as $data)
848
$OffsetList[$data['Offset']] = array('Record', $data);
849
fseek($file, 0, SEEK_END);
850
$OffsetList[ftell($file)] = 'EOF';
854
$Offsets = array_keys($OffsetList);
855
while (count($Offsets) > 1) {
856
// Don't use the EOF (which should be the last offset)
857
$ThisOffset = $Offsets[0];
858
$NextOffset = $Offsets[1];
859
if ($OffsetList[$ThisOffset] == 'EOF')
860
// Messed up file. Stop here.
863
if (is_array($OffsetList[$ThisOffset])) {
864
$FuncName .= $OffsetList[$ThisOffset][0];
865
$extraData = $OffsetList[$ThisOffset][1];
867
$FuncName .= $OffsetList[$ThisOffset];
870
fseek($file, $ThisOffset);
871
$fileData = fread($file, $NextOffset - $ThisOffset);
872
if ($this->$FuncName($fileData, $extraData))
874
array_shift($Offsets);
881
// Generic function to load the AppInfo block into $this->AppInfo
882
// Should only be called within this class
883
// Return false to signal no error
884
function LoadAppInfo($fileData) {
885
$this->AppInfo = bin2hex($fileData);
890
// Generic function to load the SortInfo block into $this->SortInfo
891
// Should only be called within this class
892
// Return false to signal no error
893
function LoadSortInfo($fileData) {
894
$this->SortInfo = bin2hex($fileData);
899
// Generic function to load a record
900
// Should only be called within this class
901
// Return false to signal no error
902
function LoadRecord($fileData, $recordInfo) {
903
$this->Records[$recordInfo['UID']] = bin2hex($fileData);
904
$this->RecordAttrs[$recordInfo['UID']] = $recordInfo['Attrs'];