Project

General

Profile

1 55 aaronmk
# XPath parsing
2 21 aaronmk
3 55 aaronmk
import copy
4 21 aaronmk
5
from Parser import Parser
6
7
class XpathElem:
8
    def __init__(self, name, value=None, attrs=None, is_attr=False,
9
        is_ptr=False):
10
        if attrs == None: attrs = []
11
        self.name = name
12
        self.value = value
13
        self.attrs = attrs
14
        self.is_attr = is_attr
15
        self.is_ptr = is_ptr
16
17
    def __repr__(self):
18
        str_ = ''
19
        if self.is_attr: str_ += '@'
20 25 aaronmk
        str_ += self.name
21
        if self.attrs != []: str_ += repr(self.attrs)
22
        if self.value != None: str_ += '='+repr(self.value)
23 21 aaronmk
        if self.is_ptr: str_ += '->'
24
        return str_
25
26
    def __eq__(self, other): return self.__dict__ == other.__dict__
27
28 24 aaronmk
def value(path): return path[-1].value
29
30 22 aaronmk
def set_value(path, value): path[-1].value = value
31
32 32 aaronmk
def backward_id(elem):
33 59 aaronmk
    if len(elem.attrs) >= 1 and value(elem.attrs[0]) == None and\
34
    len(elem.attrs[0]) == 1: return elem.attrs[0]
35 32 aaronmk
    else: return None
36
37 21 aaronmk
class XpathParser(Parser):
38
    def _main(self):
39 36 aaronmk
        self._match_str('/') # optional leading /
40 21 aaronmk
        return self._path()
41
42
    def _path(self):
43
        tree = []
44 38 aaronmk
        trailing_slash = False
45 21 aaronmk
        while True:
46 38 aaronmk
            # Split path
47
            if self._match_str('{'):
48
                paths = []
49
                while True:
50
                    paths.append(tree + self._path())
51
                    if not self._match_str(','): break
52
                self._match_str('}', required=True)
53
                tree = paths[0] # just use first subpath for now
54
                break # nothing allowed after split path
55 36 aaronmk
56 38 aaronmk
            elem = XpathElem(is_attr=self._match_str('@'),
57
                name=self._match_re(r'[\w.*]+', required=True))
58
59 36 aaronmk
            # Attrs
60 21 aaronmk
            if self._match_str('['):
61 36 aaronmk
                elem.attrs = []
62
                while True:
63
                    path = self._path()
64 38 aaronmk
                    if self._match_str('='):
65
                        set_value(path, self._match_re(r'[\w.|]*'))
66 36 aaronmk
                    elem.attrs.append(path)
67
                    if not self._match_str(','): break
68 21 aaronmk
                self._match_str(']', required=True)
69 36 aaronmk
70 21 aaronmk
            elem.is_ptr = self._match_str('->')
71
            tree.append(elem)
72 36 aaronmk
73
            # Lookahead assertion
74
            if self._match_str('('):
75
                self._match_str('/', required=True) # next / is inside ()
76
                path = self._path()
77
                self._match_str(')', required=True)
78
                elem.attrs.append(path)
79
                tree += path
80
81 21 aaronmk
            if not self._match_str('/'): break
82 32 aaronmk
83
        # Expand * abbrs
84 70 aaronmk
        for i in reversed(xrange(len(tree))):
85
            elem = tree[i]
86 32 aaronmk
            id_ = backward_id(elem)
87
            if id_ != None: elem = id_[0]; offset = -2
88
            elif elem.is_ptr: offset = 2
89
            else: offset = 1
90
            before, abbr, after = elem.name.partition('*')
91
            if abbr != '':
92 70 aaronmk
                try: elem.name = before+tree[i+offset].name+after
93 32 aaronmk
                except IndexError: pass # no replacement elem
94
95 21 aaronmk
        return tree
96
97 57 aaronmk
def parse(string): return XpathParser(string).parse()
98
99 26 aaronmk
instance_level = 1
100 22 aaronmk
101 26 aaronmk
def obj(path):
102 55 aaronmk
    obj_path = copy.deepcopy(path[:instance_level+1])
103 26 aaronmk
    obj_path[-1].is_ptr = False # prevent pointer w/o target
104
    return obj_path
105
106 22 aaronmk
def set_id(path, id_, has_types=True):
107 26 aaronmk
    if has_types: id_level = instance_level
108 21 aaronmk
    else: id_level = 0
109
    path[id_level].attrs.append([XpathElem('id', id_, is_attr=True)])
110 62 aaronmk
111
def is_id(path): return path[0].is_attr and path[0].name == 'id'
112
113
def is_instance(elem): return elem.attrs != [] and is_id(elem.attrs[0])