Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
plistlib.py949 linesDownload Raw Back to Lib
1r"""plistlib.py -- a tool to generate and parse MacOSX .plist files.2 3The property list (.plist) file format is a simple XML pickle supporting4basic object types, like dictionaries, lists, numbers and strings.5Usually the top level object is a dictionary.6 7To write out a plist file, use the dump(value, file)8function. 'value' is the top level object, 'file' is9a (writable) file object.10 11To parse a plist from a file, use the load(file) function,12with a (readable) file object as the only argument. It13returns the top level object (again, usually a dictionary).14 15To work with plist data in bytes objects, you can use loads()16and dumps().17 18Values can be strings, integers, floats, booleans, tuples, lists,19dictionaries (but only with string keys), Data, bytes, bytearray, or20datetime.datetime objects.21 22Generate Plist example:23 24    import datetime as dt25    import plistlib26 27    pl = dict(28        aString = "Doodah",29        aList = ["A", "B", 12, 32.1, [1, 2, 3]],30        aFloat = 0.1,31        anInt = 728,32        aDict = dict(33            anotherString = "<hello & hi there!>",34            aThirdString = "M\xe4ssig, Ma\xdf",35            aTrueValue = True,36            aFalseValue = False,37        ),38        someData = b"<binary gunk>",39        someMoreData = b"<lots of binary gunk>" * 10,40        aDate = dt.datetime.now()41    )42    print(plistlib.dumps(pl).decode())43 44Parse Plist example:45 46    import plistlib47 48    plist = b'''<plist version="1.0">49    <dict>50        <key>foo</key>51        <string>bar</string>52    </dict>53    </plist>'''54    pl = plistlib.loads(plist)55    print(pl["foo"])56"""57__all__ = [58    "InvalidFileException", "FMT_XML", "FMT_BINARY", "load", "dump", "loads", "dumps", "UID"59]60 61import binascii62import codecs63import datetime64import enum65from io import BytesIO66import itertools67import os68import re69import struct70from xml.parsers.expat import ParserCreate71 72 73PlistFormat = enum.Enum('PlistFormat', 'FMT_XML FMT_BINARY', module=__name__)74globals().update(PlistFormat.__members__)75 76# Data larger than this will be read in chunks, to prevent extreme77# overallocation.78_MIN_READ_BUF_SIZE = 1 << 2079 80class UID:81    def __init__(self, data):82        if not isinstance(data, int):83            raise TypeError("data must be an int")84        if data >= 1 << 64:85            raise ValueError("UIDs cannot be >= 2**64")86        if data < 0:87            raise ValueError("UIDs must be positive")88        self.data = data89 90    def __index__(self):91        return self.data92 93    def __repr__(self):94        return "%s(%s)" % (self.__class__.__name__, repr(self.data))95 96    def __reduce__(self):97        return self.__class__, (self.data,)98 99    def __eq__(self, other):100        if not isinstance(other, UID):101            return NotImplemented102        return self.data == other.data103 104    def __hash__(self):105        return hash(self.data)106 107#108# XML support109#110 111 112# XML 'header'113PLISTHEADER = b"""\114<?xml version="1.0" encoding="UTF-8"?>115<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">116"""117 118 119# Regex to find any control chars, except for \t \n and \r120_controlCharPat = re.compile(121    r"[\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c\x0e\x0f"122    r"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f]")123 124def _encode_base64(s, maxlinelength=76):125    # copied from base64.encodebytes(), with added maxlinelength argument126    maxbinsize = (maxlinelength//4)*3127    pieces = []128    for i in range(0, len(s), maxbinsize):129        chunk = s[i : i + maxbinsize]130        pieces.append(binascii.b2a_base64(chunk))131    return b''.join(pieces)132 133def _decode_base64(s):134    if isinstance(s, str):135        return binascii.a2b_base64(s.encode("utf-8"))136 137    else:138        return binascii.a2b_base64(s)139 140# Contents should conform to a subset of ISO 8601141# (in particular, YYYY '-' MM '-' DD 'T' HH ':' MM ':' SS 'Z'.  Smaller units142# may be omitted with #  a loss of precision)143_dateParser = re.compile(r"(?P<year>\d\d\d\d)(?:-(?P<month>\d\d)(?:-(?P<day>\d\d)(?:T(?P<hour>\d\d)(?::(?P<minute>\d\d)(?::(?P<second>\d\d))?)?)?)?)?Z", re.ASCII)144 145 146def _date_from_string(s, aware_datetime):147    order = ('year', 'month', 'day', 'hour', 'minute', 'second')148    gd = _dateParser.match(s).groupdict()149    lst = []150    for key in order:151        val = gd[key]152        if val is None:153            break154        lst.append(int(val))155    if aware_datetime:156        return datetime.datetime(*lst, tzinfo=datetime.UTC)157    return datetime.datetime(*lst)158 159 160def _date_to_string(d, aware_datetime):161    if aware_datetime:162        d = d.astimezone(datetime.UTC)163    return '%04d-%02d-%02dT%02d:%02d:%02dZ' % (164        d.year, d.month, d.day,165        d.hour, d.minute, d.second166    )167 168def _escape(text):169    m = _controlCharPat.search(text)170    if m is not None:171        raise ValueError("strings can't contain control characters; "172                         "use bytes instead")173    text = text.replace("\r\n", "\n")       # convert DOS line endings174    text = text.replace("\r", "\n")         # convert Mac line endings175    text = text.replace("&", "&amp;")       # escape '&'176    text = text.replace("<", "&lt;")        # escape '<'177    text = text.replace(">", "&gt;")        # escape '>'178    return text179 180class _PlistParser:181    def __init__(self, dict_type, aware_datetime=False):182        self.stack = []183        self.current_key = None184        self.root = None185        self._dict_type = dict_type186        self._aware_datetime = aware_datetime187 188    def parse(self, fileobj):189        self.parser = ParserCreate()190        self.parser.StartElementHandler = self.handle_begin_element191        self.parser.EndElementHandler = self.handle_end_element192        self.parser.CharacterDataHandler = self.handle_data193        self.parser.EntityDeclHandler = self.handle_entity_decl194        self.parser.ParseFile(fileobj)195        return self.root196 197    def handle_entity_decl(self, entity_name, is_parameter_entity, value, base, system_id, public_id, notation_name):198        # Reject plist files with entity declarations to avoid XML vulnerabilities in expat.199        # Regular plist files don't contain those declarations, and Apple's plutil tool does not200        # accept them either.201        raise InvalidFileException("XML entity declarations are not supported in plist files")202 203    def handle_begin_element(self, element, attrs):204        self.data = []205        handler = getattr(self, "begin_" + element, None)206        if handler is not None:207            handler(attrs)208 209    def handle_end_element(self, element):210        handler = getattr(self, "end_" + element, None)211        if handler is not None:212            handler()213 214    def handle_data(self, data):215        self.data.append(data)216 217    def add_object(self, value):218        if self.current_key is not None:219            if not isinstance(self.stack[-1], dict):220                raise ValueError("unexpected element at line %d" %221                                 self.parser.CurrentLineNumber)222            self.stack[-1][self.current_key] = value223            self.current_key = None224        elif not self.stack:225            # this is the root object226            self.root = value227        else:228            if not isinstance(self.stack[-1], list):229                raise ValueError("unexpected element at line %d" %230                                 self.parser.CurrentLineNumber)231            self.stack[-1].append(value)232 233    def get_data(self):234        data = ''.join(self.data)235        self.data = []236        return data237 238    # element handlers239 240    def begin_dict(self, attrs):241        d = self._dict_type()242        self.add_object(d)243        self.stack.append(d)244 245    def end_dict(self):246        if self.current_key:247            raise ValueError("missing value for key '%s' at line %d" %248                             (self.current_key,self.parser.CurrentLineNumber))249        self.stack.pop()250 251    def end_key(self):252        if self.current_key or not isinstance(self.stack[-1], dict):253            raise ValueError("unexpected key at line %d" %254                             self.parser.CurrentLineNumber)255        self.current_key = self.get_data()256 257    def begin_array(self, attrs):258        a = []259        self.add_object(a)260        self.stack.append(a)261 262    def end_array(self):263        self.stack.pop()264 265    def end_true(self):266        self.add_object(True)267 268    def end_false(self):269        self.add_object(False)270 271    def end_integer(self):272        raw = self.get_data()273        if raw.startswith('0x') or raw.startswith('0X'):274            self.add_object(int(raw, 16))275        else:276            self.add_object(int(raw))277 278    def end_real(self):279        self.add_object(float(self.get_data()))280 281    def end_string(self):282        self.add_object(self.get_data())283 284    def end_data(self):285        self.add_object(_decode_base64(self.get_data()))286 287    def end_date(self):288        self.add_object(_date_from_string(self.get_data(),289                                          aware_datetime=self._aware_datetime))290 291 292class _DumbXMLWriter:293    def __init__(self, file, indent_level=0, indent="\t"):294        self.file = file295        self.stack = []296        self._indent_level = indent_level297        self.indent = indent298 299    def begin_element(self, element):300        self.stack.append(element)301        self.writeln("<%s>" % element)302        self._indent_level += 1303 304    def end_element(self, element):305        assert self._indent_level > 0306        assert self.stack.pop() == element307        self._indent_level -= 1308        self.writeln("</%s>" % element)309 310    def simple_element(self, element, value=None):311        if value is not None:312            value = _escape(value)313            self.writeln("<%s>%s</%s>" % (element, value, element))314 315        else:316            self.writeln("<%s/>" % element)317 318    def writeln(self, line):319        if line:320            # plist has fixed encoding of utf-8321 322            # XXX: is this test needed?323            if isinstance(line, str):324                line = line.encode('utf-8')325            self.file.write(self._indent_level * self.indent)326            self.file.write(line)327        self.file.write(b'\n')328 329 330class _PlistWriter(_DumbXMLWriter):331    def __init__(332            self, file, indent_level=0, indent=b"\t", writeHeader=1,333            sort_keys=True, skipkeys=False, aware_datetime=False):334 335        if writeHeader:336            file.write(PLISTHEADER)337        _DumbXMLWriter.__init__(self, file, indent_level, indent)338        self._sort_keys = sort_keys339        self._skipkeys = skipkeys340        self._aware_datetime = aware_datetime341 342    def write(self, value):343        self.writeln("<plist version=\"1.0\">")344        self.write_value(value)345        self.writeln("</plist>")346 347    def write_value(self, value):348        if isinstance(value, str):349            self.simple_element("string", value)350 351        elif value is True:352            self.simple_element("true")353 354        elif value is False:355            self.simple_element("false")356 357        elif isinstance(value, int):358            if -1 << 63 <= value < 1 << 64:359                self.simple_element("integer", "%d" % value)360            else:361                raise OverflowError(value)362 363        elif isinstance(value, float):364            self.simple_element("real", repr(value))365 366        elif isinstance(value, dict):367            self.write_dict(value)368 369        elif isinstance(value, (bytes, bytearray)):370            self.write_bytes(value)371 372        elif isinstance(value, datetime.datetime):373            self.simple_element("date",374                                _date_to_string(value, self._aware_datetime))375 376        elif isinstance(value, (tuple, list)):377            self.write_array(value)378 379        else:380            raise TypeError("unsupported type: %s" % type(value))381 382    def write_bytes(self, data):383        self.begin_element("data")384        self._indent_level -= 1385        maxlinelength = max(386            16,387            76 - len((self.indent * self._indent_level).expandtabs()))388 389        for line in _encode_base64(data, maxlinelength).split(b"\n"):390            if line:391                self.writeln(line)392        self._indent_level += 1393        self.end_element("data")394 395    def write_dict(self, d):396        if d:397            self.begin_element("dict")398            if self._sort_keys:399                items = sorted(d.items())400            else:401                items = d.items()402 403            for key, value in items:404                if not isinstance(key, str):405                    if self._skipkeys:406                        continue407                    raise TypeError("keys must be strings")408                self.simple_element("key", key)409                self.write_value(value)410            self.end_element("dict")411 412        else:413            self.simple_element("dict")414 415    def write_array(self, array):416        if array:417            self.begin_element("array")418            for value in array:419                self.write_value(value)420            self.end_element("array")421 422        else:423            self.simple_element("array")424 425 426def _is_fmt_xml(header):427    prefixes = (b'<?xml', b'<plist')428 429    for pfx in prefixes:430        if header.startswith(pfx):431            return True432 433    # Also check for alternative XML encodings, this is slightly434    # overkill because the Apple tools (and plistlib) will not435    # generate files with these encodings.436    for bom, encoding in (437                (codecs.BOM_UTF8, "utf-8"),438                (codecs.BOM_UTF16_BE, "utf-16-be"),439                (codecs.BOM_UTF16_LE, "utf-16-le"),440                # expat does not support utf-32441                #(codecs.BOM_UTF32_BE, "utf-32-be"),442                #(codecs.BOM_UTF32_LE, "utf-32-le"),443            ):444        if not header.startswith(bom):445            continue446 447        for start in prefixes:448            prefix = bom + start.decode('ascii').encode(encoding)449            if header[:len(prefix)] == prefix:450                return True451 452    return False453 454#455# Binary Plist456#457 458 459class InvalidFileException (ValueError):460    def __init__(self, message="Invalid file"):461        ValueError.__init__(self, message)462 463_BINARY_FORMAT = {1: 'B', 2: 'H', 4: 'L', 8: 'Q'}464 465_undefined = object()466 467class _BinaryPlistParser:468    """469    Read or write a binary plist file, following the description of the binary470    format.  Raise InvalidFileException in case of error, otherwise return the471    root object.472 473    see also: http://opensource.apple.com/source/CF/CF-744.18/CFBinaryPList.c474    """475    def __init__(self, dict_type, aware_datetime=False):476        self._dict_type = dict_type477        self._aware_datime = aware_datetime478 479    def parse(self, fp):480        try:481            # The basic file format:482            # HEADER483            # object...484            # refid->offset...485            # TRAILER486            self._fp = fp487            self._fp.seek(-32, os.SEEK_END)488            trailer = self._fp.read(32)489            if len(trailer) != 32:490                raise InvalidFileException()491            (492                offset_size, self._ref_size, num_objects, top_object,493                offset_table_offset494            ) = struct.unpack('>6xBBQQQ', trailer)495            self._fp.seek(offset_table_offset)496            self._object_offsets = self._read_ints(num_objects, offset_size)497            self._objects = [_undefined] * num_objects498            return self._read_object(top_object)499 500        except (OSError, IndexError, struct.error, OverflowError,501                ValueError):502            raise InvalidFileException()503 504    def _get_size(self, tokenL):505        """ return the size of the next object."""506        if tokenL == 0xF:507            m = self._fp.read(1)[0] & 0x3508            s = 1 << m509            f = '>' + _BINARY_FORMAT[s]510            return struct.unpack(f, self._fp.read(s))[0]511 512        return tokenL513 514    def _read(self, size):515        cursize = min(size, _MIN_READ_BUF_SIZE)516        data = self._fp.read(cursize)517        while True:518            if len(data) != cursize:519                raise InvalidFileException520            if cursize == size:521                return data522            delta = min(cursize, size - cursize)523            data += self._fp.read(delta)524            cursize += delta525 526    def _read_ints(self, n, size):527        data = self._read(size * n)528        if size in _BINARY_FORMAT:529            return struct.unpack(f'>{n}{_BINARY_FORMAT[size]}', data)530        else:531            if not size:532                raise InvalidFileException()533            return tuple(int.from_bytes(data[i: i + size], 'big')534                         for i in range(0, size * n, size))535 536    def _read_refs(self, n):537        return self._read_ints(n, self._ref_size)538 539    def _read_object(self, ref):540        """541        read the object by reference.542 543        May recursively read sub-objects (content of an array/dict/set)544        """545        result = self._objects[ref]546        if result is not _undefined:547            return result548 549        offset = self._object_offsets[ref]550        self._fp.seek(offset)551        token = self._fp.read(1)[0]552        tokenH, tokenL = token & 0xF0, token & 0x0F553 554        if token == 0x00:555            result = None556 557        elif token == 0x08:558            result = False559 560        elif token == 0x09:561            result = True562 563        # The referenced source code also mentions URL (0x0c, 0x0d) and564        # UUID (0x0e), but neither can be generated using the Cocoa libraries.565 566        elif token == 0x0f:567            result = b''568 569        elif tokenH == 0x10:  # int570            result = int.from_bytes(self._fp.read(1 << tokenL),571                                    'big', signed=tokenL >= 3)572 573        elif token == 0x22: # real574            result = struct.unpack('>f', self._fp.read(4))[0]575 576        elif token == 0x23: # real577            result = struct.unpack('>d', self._fp.read(8))[0]578 579        elif token == 0x33:  # date580            f = struct.unpack('>d', self._fp.read(8))[0]581            # timestamp 0 of binary plists corresponds to 1/1/2001582            # (year of Mac OS X 10.0), instead of 1/1/1970.583            if self._aware_datime:584                epoch = datetime.datetime(2001, 1, 1, tzinfo=datetime.UTC)585            else:586                epoch = datetime.datetime(2001, 1, 1)587            result = epoch + datetime.timedelta(seconds=f)588 589        elif tokenH == 0x40:  # data590            s = self._get_size(tokenL)591            result = self._read(s)592 593        elif tokenH == 0x50:  # ascii string594            s = self._get_size(tokenL)595            data = self._read(s)596            result = data.decode('ascii')597 598        elif tokenH == 0x60:  # unicode string599            s = self._get_size(tokenL) * 2600            data = self._read(s)601            result = data.decode('utf-16be')602 603        elif tokenH == 0x80:  # UID604            # used by Key-Archiver plist files605            result = UID(int.from_bytes(self._fp.read(1 + tokenL), 'big'))606 607        elif tokenH == 0xA0:  # array608            s = self._get_size(tokenL)609            obj_refs = self._read_refs(s)610            result = []611            self._objects[ref] = result612            for x in obj_refs:613                result.append(self._read_object(x))614 615        # tokenH == 0xB0 is documented as 'ordset', but is not actually616        # implemented in the Apple reference code.617 618        # tokenH == 0xC0 is documented as 'set', but sets cannot be used in619        # plists.620 621        elif tokenH == 0xD0:  # dict622            s = self._get_size(tokenL)623            key_refs = self._read_refs(s)624            obj_refs = self._read_refs(s)625            result = self._dict_type()626            self._objects[ref] = result627            try:628                for k, o in zip(key_refs, obj_refs):629                    result[self._read_object(k)] = self._read_object(o)630            except TypeError:631                raise InvalidFileException()632        else:633            raise InvalidFileException()634 635        self._objects[ref] = result636        return result637 638def _count_to_size(count):639    if count < 1 << 8:640        return 1641 642    elif count < 1 << 16:643        return 2644 645    elif count < 1 << 32:646        return 4647 648    else:649        return 8650 651_scalars = (str, int, float, datetime.datetime, bytes)652 653class _BinaryPlistWriter (object):654    def __init__(self, fp, sort_keys, skipkeys, aware_datetime=False):655        self._fp = fp656        self._sort_keys = sort_keys657        self._skipkeys = skipkeys658        self._aware_datetime = aware_datetime659 660    def write(self, value):661 662        # Flattened object list:663        self._objlist = []664 665        # Mappings from object->objectid666        # First dict has (type(object), object) as the key,667        # second dict is used when object is not hashable and668        # has id(object) as the key.669        self._objtable = {}670        self._objidtable = {}671 672        # Create list of all objects in the plist673        self._flatten(value)674 675        # Size of object references in serialized containers676        # depends on the number of objects in the plist.677        num_objects = len(self._objlist)678        self._object_offsets = [0]*num_objects679        self._ref_size = _count_to_size(num_objects)680 681        self._ref_format = _BINARY_FORMAT[self._ref_size]682 683        # Write file header684        self._fp.write(b'bplist00')685 686        # Write object list687        for obj in self._objlist:688            self._write_object(obj)689 690        # Write refnum->object offset table691        top_object = self._getrefnum(value)692        offset_table_offset = self._fp.tell()693        offset_size = _count_to_size(offset_table_offset)694        offset_format = '>' + _BINARY_FORMAT[offset_size] * num_objects695        self._fp.write(struct.pack(offset_format, *self._object_offsets))696 697        # Write trailer698        sort_version = 0699        trailer = (700            sort_version, offset_size, self._ref_size, num_objects,701            top_object, offset_table_offset702        )703        self._fp.write(struct.pack('>5xBBBQQQ', *trailer))704 705    def _flatten(self, value):706        # First check if the object is in the object table, not used for707        # containers to ensure that two subcontainers with the same contents708        # will be serialized as distinct values.709        if isinstance(value, _scalars):710            if (type(value), value) in self._objtable:711                return712 713        elif id(value) in self._objidtable:714            return715 716        # Add to objectreference map717        refnum = len(self._objlist)718        self._objlist.append(value)719        if isinstance(value, _scalars):720            self._objtable[(type(value), value)] = refnum721        else:722            self._objidtable[id(value)] = refnum723 724        # And finally recurse into containers725        if isinstance(value, dict):726            keys = []727            values = []728            items = value.items()729            if self._sort_keys:730                items = sorted(items)731 732            for k, v in items:733                if not isinstance(k, str):734                    if self._skipkeys:735                        continue736                    raise TypeError("keys must be strings")737                keys.append(k)738                values.append(v)739 740            for o in itertools.chain(keys, values):741                self._flatten(o)742 743        elif isinstance(value, (list, tuple)):744            for o in value:745                self._flatten(o)746 747    def _getrefnum(self, value):748        if isinstance(value, _scalars):749            return self._objtable[(type(value), value)]750        else:751            return self._objidtable[id(value)]752 753    def _write_size(self, token, size):754        if size < 15:755            self._fp.write(struct.pack('>B', token | size))756 757        elif size < 1 << 8:758            self._fp.write(struct.pack('>BBB', token | 0xF, 0x10, size))759 760        elif size < 1 << 16:761            self._fp.write(struct.pack('>BBH', token | 0xF, 0x11, size))762 763        elif size < 1 << 32:764            self._fp.write(struct.pack('>BBL', token | 0xF, 0x12, size))765 766        else:767            self._fp.write(struct.pack('>BBQ', token | 0xF, 0x13, size))768 769    def _write_object(self, value):770        ref = self._getrefnum(value)771        self._object_offsets[ref] = self._fp.tell()772        if value is None:773            self._fp.write(b'\x00')774 775        elif value is False:776            self._fp.write(b'\x08')777 778        elif value is True:779            self._fp.write(b'\x09')780 781        elif isinstance(value, int):782            if value < 0:783                try:784                    self._fp.write(struct.pack('>Bq', 0x13, value))785                except struct.error:786                    raise OverflowError(value) from None787            elif value < 1 << 8:788                self._fp.write(struct.pack('>BB', 0x10, value))789            elif value < 1 << 16:790                self._fp.write(struct.pack('>BH', 0x11, value))791            elif value < 1 << 32:792                self._fp.write(struct.pack('>BL', 0x12, value))793            elif value < 1 << 63:794                self._fp.write(struct.pack('>BQ', 0x13, value))795            elif value < 1 << 64:796                self._fp.write(b'\x14' + value.to_bytes(16, 'big', signed=True))797            else:798                raise OverflowError(value)799 800        elif isinstance(value, float):801            self._fp.write(struct.pack('>Bd', 0x23, value))802 803        elif isinstance(value, datetime.datetime):804            if self._aware_datetime:805                dt = value.astimezone(datetime.UTC)806                offset = dt - datetime.datetime(2001, 1, 1, tzinfo=datetime.UTC)807                f = offset.total_seconds()808            else:809                f = (value - datetime.datetime(2001, 1, 1)).total_seconds()810            self._fp.write(struct.pack('>Bd', 0x33, f))811 812        elif isinstance(value, (bytes, bytearray)):813            self._write_size(0x40, len(value))814            self._fp.write(value)815 816        elif isinstance(value, str):817            try:818                t = value.encode('ascii')819                self._write_size(0x50, len(value))820            except UnicodeEncodeError:821                t = value.encode('utf-16be')822                self._write_size(0x60, len(t) // 2)823 824            self._fp.write(t)825 826        elif isinstance(value, UID):827            if value.data < 0:828                raise ValueError("UIDs must be positive")829            elif value.data < 1 << 8:830                self._fp.write(struct.pack('>BB', 0x80, value))831            elif value.data < 1 << 16:832                self._fp.write(struct.pack('>BH', 0x81, value))833            elif value.data < 1 << 32:834                self._fp.write(struct.pack('>BL', 0x83, value))835            elif value.data < 1 << 64:836                self._fp.write(struct.pack('>BQ', 0x87, value))837            else:838                raise OverflowError(value)839 840        elif isinstance(value, (list, tuple)):841            refs = [self._getrefnum(o) for o in value]842            s = len(refs)843            self._write_size(0xA0, s)844            self._fp.write(struct.pack('>' + self._ref_format * s, *refs))845 846        elif isinstance(value, dict):847            keyRefs, valRefs = [], []848 849            if self._sort_keys:850                rootItems = sorted(value.items())851            else:852                rootItems = value.items()853 854            for k, v in rootItems:855                if not isinstance(k, str):856                    if self._skipkeys:857                        continue858                    raise TypeError("keys must be strings")859                keyRefs.append(self._getrefnum(k))860                valRefs.append(self._getrefnum(v))861 862            s = len(keyRefs)863            self._write_size(0xD0, s)864            self._fp.write(struct.pack('>' + self._ref_format * s, *keyRefs))865            self._fp.write(struct.pack('>' + self._ref_format * s, *valRefs))866 867        else:868            raise TypeError(value)869 870 871def _is_fmt_binary(header):872    return header[:8] == b'bplist00'873 874 875#876# Generic bits877#878 879_FORMATS={880    FMT_XML: dict(881        detect=_is_fmt_xml,882        parser=_PlistParser,883        writer=_PlistWriter,884    ),885    FMT_BINARY: dict(886        detect=_is_fmt_binary,887        parser=_BinaryPlistParser,888        writer=_BinaryPlistWriter,889    )890}891 892 893def load(fp, *, fmt=None, dict_type=dict, aware_datetime=False):894    """Read a .plist file. 'fp' should be a readable and binary file object.895    Return the unpacked root object (which usually is a dictionary).896    """897    if fmt is None:898        header = fp.read(32)899        fp.seek(0)900        for info in _FORMATS.values():901            if info['detect'](header):902                P = info['parser']903                break904 905        else:906            raise InvalidFileException()907 908    else:909        P = _FORMATS[fmt]['parser']910 911    p = P(dict_type=dict_type, aware_datetime=aware_datetime)912    return p.parse(fp)913 914 915def loads(value, *, fmt=None, dict_type=dict, aware_datetime=False):916    """Read a .plist file from a bytes object.917    Return the unpacked root object (which usually is a dictionary).918    """919    if isinstance(value, str):920        if fmt == FMT_BINARY:921            raise TypeError("value must be bytes-like object when fmt is "922                            "FMT_BINARY")923        value = value.encode()924    fp = BytesIO(value)925    return load(fp, fmt=fmt, dict_type=dict_type, aware_datetime=aware_datetime)926 927 928def dump(value, fp, *, fmt=FMT_XML, sort_keys=True, skipkeys=False,929         aware_datetime=False):930    """Write 'value' to a .plist file. 'fp' should be a writable,931    binary file object.932    """933    if fmt not in _FORMATS:934        raise ValueError("Unsupported format: %r"%(fmt,))935 936    writer = _FORMATS[fmt]["writer"](fp, sort_keys=sort_keys, skipkeys=skipkeys,937                                     aware_datetime=aware_datetime)938    writer.write(value)939 940 941def dumps(value, *, fmt=FMT_XML, skipkeys=False, sort_keys=True,942          aware_datetime=False):943    """Return a bytes object with the contents for a .plist file.944    """945    fp = BytesIO()946    dump(value, fp, fmt=fmt, skipkeys=skipkeys, sort_keys=sort_keys,947         aware_datetime=aware_datetime)948    return fp.getvalue()949 
codekingpro/portable-devtools · Team Ai