mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-07 18:24:30 -04:00
Added parse_xml.py to code tree, changed libiMobileDevice to use it instead of plistlib.
This commit is contained in:
parent
5cfc3bddfe
commit
76e80cea8f
@ -9,7 +9,7 @@ __copyright__ = '2013, Gregory Riker'
|
|||||||
http://www.libimobiledevice.org/docs/html/globals.html
|
http://www.libimobiledevice.org/docs/html/globals.html
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import binascii, os, plistlib, sys, time
|
import binascii, os, sys, time
|
||||||
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from ctypes import *
|
from ctypes import *
|
||||||
@ -18,6 +18,9 @@ from datetime import datetime
|
|||||||
from calibre.constants import DEBUG, islinux, isosx, iswindows
|
from calibre.constants import DEBUG, islinux, isosx, iswindows
|
||||||
from calibre.devices.usbms.driver import debug_print
|
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):
|
class libiMobileDeviceException(Exception):
|
||||||
def __init__(self, value):
|
def __init__(self, value):
|
||||||
self.value = value
|
self.value = value
|
||||||
@ -999,7 +1002,7 @@ class libiMobileDevice():
|
|||||||
xml = POINTER(c_void_p)()
|
xml = POINTER(c_void_p)()
|
||||||
xml_len = c_long(0)
|
xml_len = c_long(0)
|
||||||
self.lib.plist_to_xml(c_void_p.from_buffer(plist), byref(xml), byref(xml_len))
|
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)
|
self.lib.plist_free(plist)
|
||||||
|
|
||||||
# To determine success, we need to inspect the returned plist
|
# To determine success, we need to inspect the returned plist
|
||||||
@ -1158,7 +1161,7 @@ class libiMobileDevice():
|
|||||||
xml = POINTER(c_void_p)()
|
xml = POINTER(c_void_p)()
|
||||||
xml_len = c_long(0)
|
xml_len = c_long(0)
|
||||||
self.lib.plist_to_xml(c_void_p.from_buffer(apps), byref(xml), byref(xml_len))
|
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 = {}
|
installed_apps = {}
|
||||||
for app in app_list:
|
for app in app_list:
|
||||||
if not applist:
|
if not applist:
|
||||||
|
290
src/calibre/devices/idevice/parse_xml.py
Executable file
290
src/calibre/devices/idevice/parse_xml.py
Executable file
@ -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 <takanori.ishikawa@gmail.com>
|
||||||
|
: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 <takanori.ishikawa@gmail.com>
|
||||||
|
: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 <plist>.")
|
||||||
|
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, "<plist> 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 <key>%s</key> (<key> elements must be contained in <dict> element)" % content)
|
||||||
|
#raise PropertyListParseError("<key> element '%s' must be in <dict> 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<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$")
|
||||||
|
|
||||||
|
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'<plist version="1.0">'
|
||||||
|
... r'<dict><key>Python</key><string>.py</string></dict>'
|
||||||
|
... r'</plist>')
|
||||||
|
{'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)
|
Loading…
x
Reference in New Issue
Block a user