@@ -590,6 +590,8 @@ def __init__(self, message="Invalid file"):
590590
591591_BINARY_FORMAT = {1 : 'B' , 2 : 'H' , 4 : 'L' , 8 : 'Q' }
592592
593+ _undefined = object ()
594+
593595class _BinaryPlistParser :
594596 """
595597 Read or write a binary plist file, following the description of the binary
@@ -620,7 +622,8 @@ def parse(self, fp):
620622 ) = struct .unpack ('>6xBBQQQ' , trailer )
621623 self ._fp .seek (offset_table_offset )
622624 self ._object_offsets = self ._read_ints (num_objects , offset_size )
623- return self ._read_object (self ._object_offsets [top_object ])
625+ self ._objects = [_undefined ] * num_objects
626+ return self ._read_object (top_object )
624627
625628 except (OSError , IndexError , struct .error ):
626629 raise InvalidFileException ()
@@ -646,71 +649,77 @@ def _read_ints(self, n, size):
646649 def _read_refs (self , n ):
647650 return self ._read_ints (n , self ._ref_size )
648651
649- def _read_object (self , offset ):
652+ def _read_object (self , ref ):
650653 """
651- read the object at offset .
654+ read the object by reference .
652655
653656 May recursively read sub-objects (content of an array/dict/set)
654657 """
658+ result = self ._objects [ref ]
659+ if result is not _undefined :
660+ return result
661+
662+ offset = self ._object_offsets [ref ]
655663 self ._fp .seek (offset )
656664 token = self ._fp .read (1 )[0 ]
657665 tokenH , tokenL = token & 0xF0 , token & 0x0F
658666
659667 if token == 0x00 :
660- return None
668+ result = None
661669
662670 elif token == 0x08 :
663- return False
671+ result = False
664672
665673 elif token == 0x09 :
666- return True
674+ result = True
667675
668676 # The referenced source code also mentions URL (0x0c, 0x0d) and
669677 # UUID (0x0e), but neither can be generated using the Cocoa libraries.
670678
671679 elif token == 0x0f :
672- return b''
680+ result = b''
673681
674682 elif tokenH == 0x10 : # int
675- return int .from_bytes (self ._fp .read (1 << tokenL ),
676- 'big' , signed = tokenL >= 3 )
683+ result = int .from_bytes (self ._fp .read (1 << tokenL ),
684+ 'big' , signed = tokenL >= 3 )
677685
678686 elif token == 0x22 : # real
679- return struct .unpack ('>f' , self ._fp .read (4 ))[0 ]
687+ result = struct .unpack ('>f' , self ._fp .read (4 ))[0 ]
680688
681689 elif token == 0x23 : # real
682- return struct .unpack ('>d' , self ._fp .read (8 ))[0 ]
690+ result = struct .unpack ('>d' , self ._fp .read (8 ))[0 ]
683691
684692 elif token == 0x33 : # date
685693 f = struct .unpack ('>d' , self ._fp .read (8 ))[0 ]
686694 # timestamp 0 of binary plists corresponds to 1/1/2001
687695 # (year of Mac OS X 10.0), instead of 1/1/1970.
688- return datetime .datetime .utcfromtimestamp (f + (31 * 365 + 8 ) * 86400 )
696+ result = datetime .datetime .utcfromtimestamp (f + (31 * 365 + 8 ) * 86400 )
689697
690698 elif tokenH == 0x40 : # data
691699 s = self ._get_size (tokenL )
692700 if self ._use_builtin_types :
693- return self ._fp .read (s )
701+ result = self ._fp .read (s )
694702 else :
695- return Data (self ._fp .read (s ))
703+ result = Data (self ._fp .read (s ))
696704
697705 elif tokenH == 0x50 : # ascii string
698706 s = self ._get_size (tokenL )
699707 result = self ._fp .read (s ).decode ('ascii' )
700- return result
708+ result = result
701709
702710 elif tokenH == 0x60 : # unicode string
703711 s = self ._get_size (tokenL )
704- return self ._fp .read (s * 2 ).decode ('utf-16be' )
712+ result = self ._fp .read (s * 2 ).decode ('utf-16be' )
705713
706714 # tokenH == 0x80 is documented as 'UID' and appears to be used for
707715 # keyed-archiving, not in plists.
708716
709717 elif tokenH == 0xA0 : # array
710718 s = self ._get_size (tokenL )
711719 obj_refs = self ._read_refs (s )
712- return [self ._read_object (self ._object_offsets [x ])
713- for x in obj_refs ]
720+ result = []
721+ self ._objects [ref ] = result
722+ result .extend (self ._read_object (x ) for x in obj_refs )
714723
715724 # tokenH == 0xB0 is documented as 'ordset', but is not actually
716725 # implemented in the Apple reference code.
@@ -723,12 +732,15 @@ def _read_object(self, offset):
723732 key_refs = self ._read_refs (s )
724733 obj_refs = self ._read_refs (s )
725734 result = self ._dict_type ()
735+ self ._objects [ref ] = result
726736 for k , o in zip (key_refs , obj_refs ):
727- result [self ._read_object (self ._object_offsets [k ])
728- ] = self ._read_object (self ._object_offsets [o ])
729- return result
737+ result [self ._read_object (k )] = self ._read_object (o )
730738
731- raise InvalidFileException ()
739+ else :
740+ raise InvalidFileException ()
741+
742+ self ._objects [ref ] = result
743+ return result
732744
733745def _count_to_size (count ):
734746 if count < 1 << 8 :
@@ -743,6 +755,8 @@ def _count_to_size(count):
743755 else :
744756 return 8
745757
758+ _scalars = (str , int , float , datetime .datetime , bytes )
759+
746760class _BinaryPlistWriter (object ):
747761 def __init__ (self , fp , sort_keys , skipkeys ):
748762 self ._fp = fp
@@ -798,24 +812,25 @@ def _flatten(self, value):
798812 # First check if the object is in the object table, not used for
799813 # containers to ensure that two subcontainers with the same contents
800814 # will be serialized as distinct values.
801- if isinstance (value , (
802- str , int , float , datetime .datetime , bytes , bytearray )):
815+ if isinstance (value , _scalars ):
803816 if (type (value ), value ) in self ._objtable :
804817 return
805818
806819 elif isinstance (value , Data ):
807820 if (type (value .data ), value .data ) in self ._objtable :
808821 return
809822
823+ elif id (value ) in self ._objidtable :
824+ return
825+
810826 # Add to objectreference map
811827 refnum = len (self ._objlist )
812828 self ._objlist .append (value )
813- try :
814- if isinstance (value , Data ):
815- self ._objtable [(type (value .data ), value .data )] = refnum
816- else :
817- self ._objtable [(type (value ), value )] = refnum
818- except TypeError :
829+ if isinstance (value , _scalars ):
830+ self ._objtable [(type (value ), value )] = refnum
831+ elif isinstance (value , Data ):
832+ self ._objtable [(type (value .data ), value .data )] = refnum
833+ else :
819834 self ._objidtable [id (value )] = refnum
820835
821836 # And finally recurse into containers
@@ -842,12 +857,11 @@ def _flatten(self, value):
842857 self ._flatten (o )
843858
844859 def _getrefnum (self , value ):
845- try :
846- if isinstance (value , Data ):
847- return self ._objtable [(type (value .data ), value .data )]
848- else :
849- return self ._objtable [(type (value ), value )]
850- except TypeError :
860+ if isinstance (value , _scalars ):
861+ return self ._objtable [(type (value ), value )]
862+ elif isinstance (value , Data ):
863+ return self ._objtable [(type (value .data ), value .data )]
864+ else :
851865 return self ._objidtable [id (value )]
852866
853867 def _write_size (self , token , size ):
0 commit comments