Renamed a 'with' variable; will be a keyword in Python 2.6.
[dom-editor.git] / Dome / Path.py
blob3e03a52c584c350a0b826a4169785af89c0c0103
1 from xml.dom import Node
3 def literal_match(node):
4 return "[ext:match('%s')]" % node.nodeValue
6 # Return a string that will match this node in an XPath.
7 def match_name(node, ns):
8 if node.nodeType == Node.TEXT_NODE:
9 return 'text()'
10 elif node.nodeType == Node.COMMENT_NODE:
11 return 'comment()'
12 elif node.nodeType == Node.ELEMENT_NODE:
13 if node.namespaceURI:
14 return '%s:%s' % (ns.prefix[node.namespaceURI], node.localName)
15 return node.nodeName
16 else:
17 return node.nodeName
19 def jump_to_sibling(src, dst, ns):
20 "Return an XPath which, given a context 'src' will move to sibling 'dst'."
22 if dst.nodeType == Node.ATTRIBUTE_NODE:
23 return 'attribute::%s/' % dst.nodeName
25 # Search forwards for 'dst', counting how many matching nodes we pass.
26 count = 0
27 check = src
28 while check != dst:
29 check = check.nextSibling
30 if not check:
31 break
32 if check.nodeName == dst.nodeName:
33 count += 1
34 if check:
35 return 'following-sibling::%s[%d]/' % (match_name(dst, ns), count)
37 # Not found - search backwards for 'dst', counting how many matching nodes we pass.
38 count = 0
39 check = src
40 while check != dst:
41 check = check.previousSibling
42 if not check:
43 raise Exception("Can't get from %s to %s!" % (src, dst))
44 if check.nodeName == dst.nodeName:
45 count += 1
46 return 'preceding-sibling::%s[%d]/' % (match_name(dst, ns), count)
48 def path_to(node):
49 "Returns a path to the node in the form [root, ... , node]"
50 ps = [node]
51 while node.parentNode:
52 node = node.parentNode
53 ps.insert(0, node)
54 return ps
56 def make_relative_path(src_node, dst_node, lit, ns):
57 "Return an XPath string which will move us from src to dst."
58 "If 'lit' then the text of the (data) node must match too."
59 "Namespace 'ns' is used to find the prefixes."
61 assert src_node
62 assert dst_node
64 if src_node == dst_node:
65 return '.'
67 src_parents = path_to(src_node)
68 dst_parents = path_to(dst_node)
70 # Trim off all the common path elements...
71 # Note that this may leave either path empty, if one node is an ancestor of the other.
72 while src_parents and dst_parents and src_parents[0] == dst_parents[0]:
73 del src_parents[0]
74 del dst_parents[0]
76 # Now, the initial context node is 'src_node'.
77 # Build the path from here...
78 path = ''
80 # We need to go up one level for each element left in src_parents, less one
81 # (so we end up as a child of the lowest common parent, on the src side).
82 # If src is an ancestor of dst then this does nothing.
83 # If dst is an ancestor of src then go up an extra level, because we don't jump
84 # across in the next step.
85 for p in range(0, len(src_parents) - 1):
86 path += '../'
87 if not dst_parents:
88 path += '../'
90 # We then jump across to the correct sibling and head back down the tree...
91 # If src is an ancestor of dst or the other way round we do nothing.
92 if src_parents and dst_parents:
93 path += jump_to_sibling(src_parents[0], dst_parents[0], ns)
94 del dst_parents[0]
96 # dst_parents is now a list of nodes to visit to get to dst.
97 for node in dst_parents:
98 prev = 1
100 p = node
101 while p.previousSibling:
102 p = p.previousSibling
103 if p.nodeName == node.nodeName:
104 prev += 1
106 path += 'child::%s[%d]/' % (match_name(node, ns), prev)
108 path = path[:-1]
109 if lit:
110 path += literal_match(dst_node)
111 #print "%s [%s]" % (path, ns)
112 return path