Project

General

Profile

1
# XPath parsing
2

    
3
import copy
4

    
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
        str_ += self.name
21
        if self.attrs != []: str_ += repr(self.attrs)
22
        if self.value != None: str_ += '='+repr(self.value)
23
        if self.is_ptr: str_ += '->'
24
        return str_
25
    
26
    def __eq__(self, other): return self.__dict__ == other.__dict__
27

    
28
def value(path): return path[-1].value
29

    
30
def set_value(path, value): path[-1].value = value
31

    
32
def backward_id(elem):
33
    if len(elem.attrs) >= 1 and value(elem.attrs[0]) == None and\
34
    len(elem.attrs[0]) == 1: return elem.attrs[0]
35
    else: return None
36

    
37
class XpathParser(Parser):
38
    def _main(self):
39
        self._match_str('/') # optional leading /
40
        return self._path()
41
    
42
    def _path(self):
43
        tree = []
44
        trailing_slash = False
45
        while True:
46
            # 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
            
56
            elem = XpathElem(is_attr=self._match_str('@'),
57
                name=self._match_re(r'[\w.*]+', required=True))
58
            
59
            # Attrs
60
            if self._match_str('['):
61
                elem.attrs = []
62
                while True:
63
                    path = self._path()
64
                    if self._match_str('='):
65
                        set_value(path, self._match_re(r'[\w.|]*'))
66
                    elem.attrs.append(path)
67
                    if not self._match_str(','): break
68
                self._match_str(']', required=True)
69
            
70
            elem.is_ptr = self._match_str('->')
71
            tree.append(elem)
72
            
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
            if not self._match_str('/'): break
82
        
83
        # Expand * abbrs
84
        for i in reversed(xrange(len(tree))):
85
            elem = tree[i]
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[i+offset].name+after
93
                except IndexError: pass # no replacement elem
94
        
95
        return tree
96

    
97
def parse(string): return XpathParser(string).parse()
98

    
99
instance_level = 1
100

    
101
def obj(path):
102
    obj_path = copy.deepcopy(path[:instance_level+1])
103
    obj_path[-1].is_ptr = False # prevent pointer w/o target
104
    return obj_path
105

    
106
def set_id(path, id_, has_types=True):
107
    if has_types: id_level = instance_level
108
    else: id_level = 0
109
    path[id_level].attrs.append([XpathElem('id', id_, is_attr=True)])
110

    
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]) 
(9-9/9)