-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathparser.py
More file actions
138 lines (114 loc) · 5.69 KB
/
parser.py
File metadata and controls
138 lines (114 loc) · 5.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
"""An xml parser able to bind xml tags to an object methods"""
# pylint: disable=line-too-long
import re
from lxml import etree
from xmlcommandparser.helpers import fromjson, sanitize_filepath
from xmlcommandparser.exceptions import InvalidCommandException, InvalidAttrException, InvalidSourceException, InvalidFormatterSpecified
class XmlCommandParser(object):
"""An xml parser that acts as a bridge between xml tags and python methods"""
def __init__(self, document):
try:
self.root = etree.fromstring(document.source)
except Exception as err:
errmsg = "-- Invalid source -- \n-- SOURCE START --\n{source}\n-- SOURCE END --\n{err}"
raise InvalidSourceException(errmsg.format(source=document.source, err=err))
self.document = document
self.formatters = ['{int}', '{json}', '{float}', '{path}']
def parse_commands(self, root_tag, obj):
"""Parse children of the specified :root_tag: element
:param str: root_tag tag to parse (e.g. 'body')
:param object: obj object whose methods can be used as xml tags
"""
container = self.root.find(root_tag)
if hasattr(obj, 'before_commands'):
obj.before_commands(container=container)
for command in container.getchildren():
fnc = getattr(obj, command.tag, None)
if not fnc:
raise InvalidCommandException("Invalid command: {tag} not found".format(tag=command.tag))
if not hasattr(fnc, '__xmlcommand__'):
raise InvalidCommandException('Invalid command {}, does not match any callables'.format(command.tag))
kwargs = self.normalize_xml_attributes(command)
if hasattr(obj, 'before_command'):
kwargs = obj.before_command(container=container, element=command, kwargs=kwargs)
try:
fnc(**kwargs)
except Exception as err:
errmsg = "Error parsing command: {tag} with args {args}. {err}"
import sys, traceback
exc = sys.exc_info()
errstr = ''.join(traceback.format_exception(*exc))
raise InvalidCommandException(errmsg.format(tag=command.tag, args=kwargs, err=errstr))
if hasattr(obj, 'after_command'):
obj.after_command(container=container, element=command, kwargs=kwargs)
if hasattr(obj, 'after_commands'):
obj.after_commands(container=container)
def normalize_xml_attributes(self, element):
"""removes the namespace from the attribute name"""
# pylint: disable=unused-variable
attrs = {}
for attrname, attrvalue in element.attrib.iteritems():
attrname = attrname[attrname.find('}')+1:]
# do not use attrvalue here
attrs[attrname] = self.get_xml_attr(element, attrname)
return attrs
def apply_document_args(self, attrvalue):
"""format() attrvalue using document_args"""
attrvalue = attrvalue.replace('{{', '[[')
attrvalue = attrvalue.replace('}}', ']]')
formatted = attrvalue.format(**self.document.document_args)
formatted = formatted.replace('[[', '{{')
formatted = formatted.replace(']]', '}}')
return formatted
def get_xml_attr(self, element, attrname):
"""parse attribute value using the namespace"""
attrvalue = element.get(attrname)
if attrvalue:
return self.apply_document_args(attrvalue)
else:
# has a namespace
regex = re.compile(r'(^\{.*\})('+attrname+')')
for name, value in element.attrib.iteritems():
match = regex.search(name)
if match:
attrkey = match.group(0)
formatter = match.group(1)
value = element.get(attrkey)
return self.apply_ns_format(formatter, value)
raise InvalidFormatterSpecified('Can not parse value for {}'.format(attrname))
def apply_ns_format(self, fmt, value):
"""format attribute value using the formatter specified in the namespace"""
if fmt == '{int}':
try:
return int(value)
except Exception as err:
errmsg = "Can not parse {value} as int: {err}"
raise InvalidAttrException(errmsg.format(value=value, err=err))
elif fmt == '{json}':
try:
value = fromjson(value)
except ValueError as err:
errmsg = "Can not parse {value} as json: {err}"
raise InvalidAttrException(errmsg.format(value=value, err=err))
if hasattr(value, 'format'):
value = self.apply_document_args(value)
return value
elif fmt == '{float}':
try:
return float(value)
except Exception as err:
errmsg = "Can not parse {value} as float: {err}"
raise InvalidAttrException(errmsg.format(value=value, err=err))
elif fmt == '{path}':
try:
return sanitize_filepath(self.document.env, value)
except Exception as err:
errmsg = "Can not parse {value} as path: {err}"
raise InvalidAttrException(errmsg.format(value=value, err=err))
else:
return self.apply_formatter(fmt, value)
def apply_formatter(self, fmt, value):
# pylint: disable=unused-argument,no-self-use
"""extends this to use custom formatters"""
errmsg = 'Formatter {} not found. Maybe you should extends the XmlCommandParser class'
raise NotImplementedError(errmsg.format(fmt))