You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
217 lines
5.3 KiB
217 lines
5.3 KiB
6 years ago
|
from plex.lib.six import string_types, StringIO
|
||
|
from plex.lib.six.moves.urllib_parse import urlparse
|
||
|
|
||
|
from functools import wraps
|
||
|
import logging
|
||
|
|
||
|
# Import available parser
|
||
|
PARSER = None
|
||
|
|
||
|
try:
|
||
|
from lxml import etree
|
||
|
PARSER = 'etree.HTMLParser'
|
||
|
except ImportError:
|
||
|
from xml.etree import ElementTree as etree
|
||
|
PARSER = 'etree.XMLParser'
|
||
|
|
||
|
log = logging.getLogger(__name__)
|
||
|
|
||
|
|
||
|
class Helpers(object):
|
||
|
@staticmethod
|
||
|
def get(node, attr):
|
||
|
if PARSER == 'etree.HTMLParser':
|
||
|
return node.get(attr.lower())
|
||
|
|
||
|
return node.get(attr)
|
||
|
|
||
|
@staticmethod
|
||
|
def find(node, tag):
|
||
|
if PARSER == 'etree.HTMLParser':
|
||
|
return node.find(tag.lower())
|
||
|
|
||
|
return node.find(tag)
|
||
|
|
||
|
@staticmethod
|
||
|
def findall(node, tag):
|
||
|
if PARSER == 'etree.HTMLParser':
|
||
|
return node.findall(tag.lower())
|
||
|
|
||
|
return node.findall(tag)
|
||
|
|
||
|
|
||
|
class Interface(object):
|
||
|
helpers = Helpers
|
||
|
|
||
|
path = None
|
||
|
object_map = {}
|
||
|
|
||
|
def __init__(self, client):
|
||
|
self.client = client
|
||
|
|
||
|
def __getitem__(self, name):
|
||
|
if hasattr(self, name):
|
||
|
return getattr(self, name)
|
||
|
|
||
|
raise ValueError('Unknown action "%s" on %s', name, self)
|
||
|
|
||
|
@property
|
||
|
def http(self):
|
||
|
if not self.client:
|
||
|
return None
|
||
|
|
||
|
return self.client.http.configure(self.path)
|
||
|
|
||
|
def parse(self, response, schema):
|
||
|
if response.status_code < 200 or response.status_code >= 300:
|
||
|
return None
|
||
|
|
||
|
try:
|
||
|
root = self.__parse_xml(response.content)
|
||
|
except SyntaxError as ex:
|
||
|
log.error('Unable to parse XML response: %s', ex, exc_info=True, extra={
|
||
|
'data': {
|
||
|
'snippet': self.__error_snippet(response, ex)
|
||
|
}
|
||
|
})
|
||
|
|
||
|
return None
|
||
|
except Exception as ex:
|
||
|
log.error('Unable to parse XML response: %s', ex, exc_info=True)
|
||
|
|
||
|
return None
|
||
|
|
||
|
url = urlparse(response.url)
|
||
|
path = url.path
|
||
|
|
||
|
return self.__construct(self.client, path, root, schema)
|
||
|
|
||
|
@staticmethod
|
||
|
def __parse_xml(content):
|
||
|
if PARSER == 'etree.HTMLParser':
|
||
|
html = etree.fromstring(content, parser=etree.HTMLParser())
|
||
|
assert html.tag == 'html'
|
||
|
|
||
|
bodies = [e for e in html if e.tag == 'body']
|
||
|
assert len(bodies) == 1
|
||
|
|
||
|
body = bodies[0]
|
||
|
assert len(body) == 1
|
||
|
|
||
|
return body[0]
|
||
|
|
||
|
return etree.fromstring(content)
|
||
|
|
||
|
@staticmethod
|
||
|
def __error_snippet(response, ex):
|
||
|
# Retrieve the error line
|
||
|
position = getattr(ex, 'position', None)
|
||
|
|
||
|
if not position or len(position) != 2:
|
||
|
return None
|
||
|
|
||
|
n_line, n_column = position
|
||
|
snippet = None
|
||
|
|
||
|
# Create StringIO stream
|
||
|
stream = StringIO(response.text)
|
||
|
|
||
|
# Iterate over `content` to find `n_line`
|
||
|
for x, l in enumerate(stream):
|
||
|
if x < n_line - 1:
|
||
|
continue
|
||
|
|
||
|
# Line found
|
||
|
snippet = l
|
||
|
break
|
||
|
|
||
|
# Close the stream
|
||
|
stream.close()
|
||
|
|
||
|
if not snippet:
|
||
|
# Couldn't find the line
|
||
|
return None
|
||
|
|
||
|
# Find an attribute value containing `n_column`
|
||
|
start = snippet.find('"', n_column)
|
||
|
end = snippet.find('"', start + 1)
|
||
|
|
||
|
# Trim `snippet` (if attribute value was found)
|
||
|
if start >= 0 and end >= 0:
|
||
|
return snippet[start:end + 1]
|
||
|
|
||
|
return snippet
|
||
|
|
||
|
@classmethod
|
||
|
def __construct(cls, client, path, node, schema):
|
||
|
if not schema:
|
||
|
return None
|
||
|
|
||
|
# Try retrieve schema for `tag`
|
||
|
item = schema.get(node.tag)
|
||
|
|
||
|
if item is None:
|
||
|
raise ValueError('Unknown node with tag "%s"' % node.tag)
|
||
|
|
||
|
if type(item) is dict:
|
||
|
value = cls.helpers.get(node, item.get('_', 'type'))
|
||
|
|
||
|
if value is None:
|
||
|
return None
|
||
|
|
||
|
item = item.get(value)
|
||
|
|
||
|
if item is None:
|
||
|
raise ValueError('Unknown node type "%s"' % value)
|
||
|
|
||
|
descriptor = None
|
||
|
child_schema = None
|
||
|
|
||
|
if type(item) is tuple and len(item) == 2:
|
||
|
descriptor, child_schema = item
|
||
|
else:
|
||
|
descriptor = item
|
||
|
|
||
|
if isinstance(descriptor, string_types):
|
||
|
if descriptor not in cls.object_map:
|
||
|
raise Exception('Unable to find descriptor by name "%s"' % descriptor)
|
||
|
|
||
|
descriptor = cls.object_map.get(descriptor)
|
||
|
|
||
|
if descriptor is None:
|
||
|
raise Exception('Unable to find descriptor')
|
||
|
|
||
|
keys_used, obj = descriptor.construct(client, node, path=path)
|
||
|
|
||
|
# Lazy-construct children
|
||
|
def iter_children():
|
||
|
for child_node in node:
|
||
|
item = cls.__construct(client, path, child_node, child_schema)
|
||
|
|
||
|
if item:
|
||
|
yield item
|
||
|
|
||
|
obj._children = iter_children()
|
||
|
|
||
|
return obj
|
||
|
|
||
|
|
||
|
class InterfaceProxy(object):
|
||
|
def __init__(self, interface, args):
|
||
|
self.interface = interface
|
||
|
self.args = list(args)
|
||
|
|
||
|
def __getattr__(self, name):
|
||
|
value = getattr(self.interface, name)
|
||
|
|
||
|
if not hasattr(value, '__call__'):
|
||
|
return value
|
||
|
|
||
|
@wraps(value)
|
||
|
def wrap(*args, **kwargs):
|
||
|
args = self.args + list(args)
|
||
|
|
||
|
return value(*args, **kwargs)
|
||
|
|
||
|
return wrap
|