From 76e80cea8f43307aa4420006094e291213b74030 Mon Sep 17 00:00:00 2001 From: GRiker Date: Fri, 26 Apr 2013 11:56:27 -0600 Subject: [PATCH] Added parse_xml.py to code tree, changed libiMobileDevice to use it instead of plistlib. --- .../devices/idevice/libimobiledevice.py | 9 +- src/calibre/devices/idevice/parse_xml.py | 290 ++++++++++++++++++ 2 files changed, 296 insertions(+), 3 deletions(-) create mode 100755 src/calibre/devices/idevice/parse_xml.py diff --git a/src/calibre/devices/idevice/libimobiledevice.py b/src/calibre/devices/idevice/libimobiledevice.py index e60e20dfc9..86062dad76 100644 --- a/src/calibre/devices/idevice/libimobiledevice.py +++ b/src/calibre/devices/idevice/libimobiledevice.py @@ -9,7 +9,7 @@ __copyright__ = '2013, Gregory Riker' http://www.libimobiledevice.org/docs/html/globals.html ''' -import binascii, os, plistlib, sys, time +import binascii, os, sys, time from collections import OrderedDict from ctypes import * @@ -18,6 +18,9 @@ from datetime import datetime from calibre.constants import DEBUG, islinux, isosx, iswindows from calibre.devices.usbms.driver import debug_print +from calibre_plugins.marvin.parse_xml import XmlPropertyListParser +#from calibre.devices.idevice.parse_xml import XmlPropertyListParser + class libiMobileDeviceException(Exception): def __init__(self, value): self.value = value @@ -999,7 +1002,7 @@ class libiMobileDevice(): xml = POINTER(c_void_p)() xml_len = c_long(0) self.lib.plist_to_xml(c_void_p.from_buffer(plist), byref(xml), byref(xml_len)) - result = plistlib.readPlistFromString(string_at(xml, xml_len.value)) + result = XmlPropertyListParser().parse(string_at(xml, xml_len.value)) self.lib.plist_free(plist) # To determine success, we need to inspect the returned plist @@ -1158,7 +1161,7 @@ class libiMobileDevice(): xml = POINTER(c_void_p)() xml_len = c_long(0) self.lib.plist_to_xml(c_void_p.from_buffer(apps), byref(xml), byref(xml_len)) - app_list = plistlib.readPlistFromString(string_at(xml, xml_len.value)) + app_list = XmlPropertyListParser().parse(string_at(xml, xml_len.value)) installed_apps = {} for app in app_list: if not applist: diff --git a/src/calibre/devices/idevice/parse_xml.py b/src/calibre/devices/idevice/parse_xml.py new file mode 100755 index 0000000000..562ce22025 --- /dev/null +++ b/src/calibre/devices/idevice/parse_xml.py @@ -0,0 +1,290 @@ +#!/usr/bin/env python +""" +https://github.com/ishikawa/python-plist-parser/blob/master/plist_parser.py + +A `Property Lists`_ is a data representation used in Apple's Mac OS X as +a convenient way to store standard object types, such as string, number, +boolean, and container object. + +This file contains a class ``XmlPropertyListParser`` for parse +a property list file and get back a python native data structure. + + :copyright: 2008 by Takanori Ishikawa + :license: MIT (See LICENSE file for more details) + +.. _Property Lists: http://developer.apple.com/documentation/Cocoa/Conceptual/PropertyLists/ +""" + +class PropertyListParseError(Exception): + """Raised when parsing a property list is failed.""" + pass + + +class XmlPropertyListParser(object): + """ + The ``XmlPropertyListParser`` class provides methods that + convert `Property Lists`_ objects from xml format. + Property list objects include ``string``, ``unicode``, + ``list``, ``dict``, ``datetime``, and ``int`` or ``float``. + + :copyright: 2008 by Takanori Ishikawa + :license: MIT License + + .. _Property List: http://developer.apple.com/documentation/Cocoa/Conceptual/PropertyLists/ + """ + + def _assert(self, test, message): + if not test: + raise PropertyListParseError(message) + + # ------------------------------------------------ + # SAX2: ContentHandler + # ------------------------------------------------ + def setDocumentLocator(self, locator): + pass + def startPrefixMapping(self, prefix, uri): + pass + def endPrefixMapping(self, prefix): + pass + def startElementNS(self, name, qname, attrs): + pass + def endElementNS(self, name, qname): + pass + def ignorableWhitespace(self, whitespace): + pass + def processingInstruction(self, target, data): + pass + def skippedEntity(self, name): + pass + + def startDocument(self): + self.__stack = [] + self.__plist = self.__key = self.__characters = None + # For reducing runtime type checking, + # the parser caches top level object type. + self.__in_dict = False + + def endDocument(self): + self._assert(self.__plist is not None, "A top level element must be .") + self._assert( + len(self.__stack) is 0, + "multiple objects at top level.") + + def startElement(self, name, attributes): + if name in XmlPropertyListParser.START_CALLBACKS: + XmlPropertyListParser.START_CALLBACKS[name](self, name, attributes) + if name in XmlPropertyListParser.PARSE_CALLBACKS: + self.__characters = [] + + def endElement(self, name): + if name in XmlPropertyListParser.END_CALLBACKS: + XmlPropertyListParser.END_CALLBACKS[name](self, name) + if name in XmlPropertyListParser.PARSE_CALLBACKS: + # Creates character string from buffered characters. + content = ''.join(self.__characters) + # For compatibility with ``xml.etree`` and ``plistlib``, + # convert text string to ascii, if possible + try: + content = content.encode('ascii') + except (UnicodeError, AttributeError): + pass + XmlPropertyListParser.PARSE_CALLBACKS[name](self, name, content) + self.__characters = None + + def characters(self, content): + if self.__characters is not None: + self.__characters.append(content) + + # ------------------------------------------------ + # XmlPropertyListParser private + # ------------------------------------------------ + def _push_value(self, value): + if not self.__stack: + self._assert(self.__plist is None, "Multiple objects at top level") + self.__plist = value + else: + top = self.__stack[-1] + #assert isinstance(top, (dict, list)) + if self.__in_dict: + k = self.__key + if k is None: + raise PropertyListParseError("Missing key for dictionary.") + top[k] = value + self.__key = None + else: + top.append(value) + + def _push_stack(self, value): + self.__stack.append(value) + self.__in_dict = isinstance(value, dict) + + def _pop_stack(self): + self.__stack.pop() + self.__in_dict = self.__stack and isinstance(self.__stack[-1], dict) + + def _start_plist(self, name, attrs): + self._assert(not self.__stack and self.__plist is None, " more than once.") + self._assert(attrs.get('version', '1.0') == '1.0', + "version 1.0 is only supported, but was '%s'." % attrs.get('version')) + + def _start_array(self, name, attrs): + v = list() + self._push_value(v) + self._push_stack(v) + + def _start_dict(self, name, attrs): + v = dict() + self._push_value(v) + self._push_stack(v) + + def _end_array(self, name): + self._pop_stack() + + def _end_dict(self, name): + if self.__key is not None: + raise PropertyListParseError("Missing value for key '%s'" % self.__key) + self._pop_stack() + + def _start_true(self, name, attrs): + self._push_value(True) + + def _start_false(self, name, attrs): + self._push_value(False) + + def _parse_key(self, name, content): + if not self.__in_dict: + print("XmlPropertyListParser() WARNING: ignoring %s ( elements must be contained in element)" % content) + #raise PropertyListParseError(" element '%s' must be in element." % content) + else: + self.__key = content + + def _parse_string(self, name, content): + self._push_value(content) + + def _parse_data(self, name, content): + import base64 + self._push_value(base64.b64decode(content)) + + # http://www.apple.com/DTDs/PropertyList-1.0.dtd says: + # + # Contents should conform to a subset of ISO 8601 + # (in particular, YYYY '-' MM '-' DD 'T' HH ':' MM ':' SS 'Z'. + # Smaller units may be omitted with a loss of precision) + import re + DATETIME_PATTERN = re.compile(r"(?P\d\d\d\d)(?:-(?P\d\d)(?:-(?P\d\d)(?:T(?P\d\d)(?::(?P\d\d)(?::(?P\d\d))?)?)?)?)?Z$") + + def _parse_date(self, name, content): + import datetime + + units = ('year', 'month', 'day', 'hour', 'minute', 'second', ) + pattern = XmlPropertyListParser.DATETIME_PATTERN + match = pattern.match(content) + if not match: + raise PropertyListParseError("Failed to parse datetime '%s'" % content) + + groups, components = match.groupdict(), [] + for key in units: + value = groups[key] + if value is None: + break + components.append(int(value)) + while len(components) < 3: + components.append(1) + + d = datetime.datetime(*components) + self._push_value(d) + + def _parse_real(self, name, content): + self._push_value(float(content)) + + def _parse_integer(self, name, content): + self._push_value(int(content)) + + START_CALLBACKS = { + 'plist': _start_plist, + 'array': _start_array, + 'dict': _start_dict, + 'true': _start_true, + 'false': _start_false, + } + + END_CALLBACKS = { + 'array': _end_array, + 'dict': _end_dict, + } + + PARSE_CALLBACKS = { + 'key': _parse_key, + 'string': _parse_string, + 'data': _parse_data, + 'date': _parse_date, + 'real': _parse_real, + 'integer': _parse_integer, + } + + # ------------------------------------------------ + # XmlPropertyListParser + # ------------------------------------------------ + def _to_stream(self, io_or_string): + if isinstance(io_or_string, basestring): + # Creates a string stream for in-memory contents. + from cStringIO import StringIO + return StringIO(io_or_string) + elif hasattr(io_or_string, 'read') and callable(getattr(io_or_string, 'read')): + return io_or_string + else: + raise TypeError('Can\'t convert %s to file-like-object' % type(io_or_string)) + + def _parse_using_etree(self, xml_input): + from xml.etree.cElementTree import iterparse + + parser = iterparse(self._to_stream(xml_input), events=('start', 'end')) + self.startDocument() + try: + for action, element in parser: + name = element.tag + if action == 'start': + if name in XmlPropertyListParser.START_CALLBACKS: + XmlPropertyListParser.START_CALLBACKS[name](self, element.tag, element.attrib) + elif action == 'end': + if name in XmlPropertyListParser.END_CALLBACKS: + XmlPropertyListParser.END_CALLBACKS[name](self, name) + if name in XmlPropertyListParser.PARSE_CALLBACKS: + XmlPropertyListParser.PARSE_CALLBACKS[name](self, name, element.text or "") + element.clear() + except SyntaxError, e: + raise PropertyListParseError(e) + + self.endDocument() + return self.__plist + + def _parse_using_sax_parser(self, xml_input): + from xml.sax import make_parser, handler, xmlreader, \ + SAXParseException + source = xmlreader.InputSource() + source.setByteStream(self._to_stream(xml_input)) + reader = make_parser() + reader.setContentHandler(self) + try: + reader.parse(source) + except SAXParseException, e: + raise PropertyListParseError(e) + + return self.__plist + + def parse(self, xml_input): + """ + Parse the property list (`.plist`, `.xml, for example) ``xml_input``, + which can be either a string or a file-like object. + + >>> parser = XmlPropertyListParser() + >>> parser.parse(r'' + ... r'Python.py' + ... r'') + {'Python': '.py'} + """ + try: + return self._parse_using_etree(xml_input) + except ImportError: + # No xml.etree.ccElementTree found. + return self._parse_using_sax_parser(xml_input) \ No newline at end of file