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
    if len(elem.attrs) >= 1 and value(elem.attrs[0]) == None:
34
        return elem.attrs[0]
35
    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
        elem_idx = 0
85
        for elem in tree:
86
            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
                try: elem.name = before+tree[elem_idx+offset].name+after
93
                except IndexError: pass # no replacement elem
94
            elem_idx += 1
95
96 21 aaronmk
        return tree
97
98 57 aaronmk
def parse(string): return XpathParser(string).parse()
99
100 26 aaronmk
instance_level = 1
101 22 aaronmk
102 26 aaronmk
def obj(path):
103 55 aaronmk
    obj_path = copy.deepcopy(path[:instance_level+1])
104 26 aaronmk
    obj_path[-1].is_ptr = False # prevent pointer w/o target
105
    return obj_path
106
107 22 aaronmk
def set_id(path, id_, has_types=True):
108 26 aaronmk
    if has_types: id_level = instance_level
109 21 aaronmk
    else: id_level = 0
110
    path[id_level].attrs.append([XpathElem('id', id_, is_attr=True)])