| OLD | NEW |
| (Empty) |
| 1 # -*- test-case-name: twisted.words.test.test_xpath -*- | |
| 2 # | |
| 3 # Copyright (c) 2001-2007 Twisted Matrix Laboratories. | |
| 4 # See LICENSE for details. | |
| 5 | |
| 6 """ | |
| 7 XPath query support. | |
| 8 | |
| 9 This module provides L{XPathQuery} to match | |
| 10 L{domish.Element<twisted.words.xish.domish.Element>} instances against | |
| 11 XPath-like expressions. | |
| 12 """ | |
| 13 | |
| 14 try: | |
| 15 import cStringIO as StringIO | |
| 16 except ImportError: | |
| 17 import StringIO | |
| 18 | |
| 19 class LiteralValue(str): | |
| 20 def value(self, elem): | |
| 21 return self | |
| 22 | |
| 23 | |
| 24 class IndexValue: | |
| 25 def __init__(self, index): | |
| 26 self.index = int(index) - 1 | |
| 27 | |
| 28 def value(self, elem): | |
| 29 return elem.children[self.index] | |
| 30 | |
| 31 | |
| 32 class AttribValue: | |
| 33 def __init__(self, attribname): | |
| 34 self.attribname = attribname | |
| 35 if self.attribname == "xmlns": | |
| 36 self.value = self.value_ns | |
| 37 | |
| 38 def value_ns(self, elem): | |
| 39 return elem.uri | |
| 40 | |
| 41 def value(self, elem): | |
| 42 if self.attribname in elem.attributes: | |
| 43 return elem.attributes[self.attribname] | |
| 44 else: | |
| 45 return None | |
| 46 | |
| 47 | |
| 48 class CompareValue: | |
| 49 def __init__(self, lhs, op, rhs): | |
| 50 self.lhs = lhs | |
| 51 self.rhs = rhs | |
| 52 if op == "=": | |
| 53 self.value = self._compareEqual | |
| 54 else: | |
| 55 self.value = self._compareNotEqual | |
| 56 | |
| 57 def _compareEqual(self, elem): | |
| 58 return self.lhs.value(elem) == self.rhs.value(elem) | |
| 59 | |
| 60 def _compareNotEqual(self, elem): | |
| 61 return self.lhs.value(elem) != self.rhs.value(elem) | |
| 62 | |
| 63 | |
| 64 class BooleanValue: | |
| 65 """ | |
| 66 Provide boolean XPath expression operators. | |
| 67 | |
| 68 @ivar lhs: Left hand side expression of the operator. | |
| 69 @ivar op: The operator. One of C{'and'}, C{'or'}. | |
| 70 @ivar rhs: Right hand side expression of the operator. | |
| 71 @ivar value: Reference to the method that will calculate the value of | |
| 72 this expression given an element. | |
| 73 """ | |
| 74 def __init__(self, lhs, op, rhs): | |
| 75 self.lhs = lhs | |
| 76 self.rhs = rhs | |
| 77 if op == "and": | |
| 78 self.value = self._booleanAnd | |
| 79 else: | |
| 80 self.value = self._booleanOr | |
| 81 | |
| 82 def _booleanAnd(self, elem): | |
| 83 """ | |
| 84 Calculate boolean and of the given expressions given an element. | |
| 85 | |
| 86 @param elem: The element to calculate the value of the expression from. | |
| 87 """ | |
| 88 return self.lhs.value(elem) and self.rhs.value(elem) | |
| 89 | |
| 90 def _booleanOr(self, elem): | |
| 91 """ | |
| 92 Calculate boolean or of the given expressions given an element. | |
| 93 | |
| 94 @param elem: The element to calculate the value of the expression from. | |
| 95 """ | |
| 96 return self.lhs.value(elem) or self.rhs.value(elem) | |
| 97 | |
| 98 | |
| 99 def Function(fname): | |
| 100 """ | |
| 101 Internal method which selects the function object | |
| 102 """ | |
| 103 klassname = "_%s_Function" % fname | |
| 104 c = globals()[klassname]() | |
| 105 return c | |
| 106 | |
| 107 | |
| 108 class _not_Function: | |
| 109 def __init__(self): | |
| 110 self.baseValue = None | |
| 111 | |
| 112 def setParams(self, baseValue): | |
| 113 self.baseValue = baseValue | |
| 114 | |
| 115 def value(self, elem): | |
| 116 return not self.baseValue.value(elem) | |
| 117 | |
| 118 | |
| 119 class _text_Function: | |
| 120 def setParams(self): | |
| 121 pass | |
| 122 | |
| 123 def value(self, elem): | |
| 124 return str(elem) | |
| 125 | |
| 126 | |
| 127 class _Location: | |
| 128 def __init__(self): | |
| 129 self.predicates = [] | |
| 130 self.elementName = None | |
| 131 self.childLocation = None | |
| 132 | |
| 133 def matchesPredicates(self, elem): | |
| 134 if self.elementName != None and self.elementName != elem.name: | |
| 135 return 0 | |
| 136 | |
| 137 for p in self.predicates: | |
| 138 if not p.value(elem): | |
| 139 return 0 | |
| 140 | |
| 141 return 1 | |
| 142 | |
| 143 def matches(self, elem): | |
| 144 if not self.matchesPredicates(elem): | |
| 145 return 0 | |
| 146 | |
| 147 if self.childLocation != None: | |
| 148 for c in elem.elements(): | |
| 149 if self.childLocation.matches(c): | |
| 150 return 1 | |
| 151 else: | |
| 152 return 1 | |
| 153 | |
| 154 return 0 | |
| 155 | |
| 156 def queryForString(self, elem, resultbuf): | |
| 157 if not self.matchesPredicates(elem): | |
| 158 return | |
| 159 | |
| 160 if self.childLocation != None: | |
| 161 for c in elem.elements(): | |
| 162 self.childLocation.queryForString(c, resultbuf) | |
| 163 else: | |
| 164 resultbuf.write(str(elem)) | |
| 165 | |
| 166 def queryForNodes(self, elem, resultlist): | |
| 167 if not self.matchesPredicates(elem): | |
| 168 return | |
| 169 | |
| 170 if self.childLocation != None: | |
| 171 for c in elem.elements(): | |
| 172 self.childLocation.queryForNodes(c, resultlist) | |
| 173 else: | |
| 174 resultlist.append(elem) | |
| 175 | |
| 176 def queryForStringList(self, elem, resultlist): | |
| 177 if not self.matchesPredicates(elem): | |
| 178 return | |
| 179 | |
| 180 if self.childLocation != None: | |
| 181 for c in elem.elements(): | |
| 182 self.childLocation.queryForStringList(c, resultlist) | |
| 183 else: | |
| 184 for c in elem.children: | |
| 185 if isinstance(c, (str, unicode)): | |
| 186 resultlist.append(c) | |
| 187 | |
| 188 | |
| 189 class _AnyLocation: | |
| 190 def __init__(self): | |
| 191 self.predicates = [] | |
| 192 self.elementName = None | |
| 193 self.childLocation = None | |
| 194 | |
| 195 def matchesPredicates(self, elem): | |
| 196 for p in self.predicates: | |
| 197 if not p.value(elem): | |
| 198 return 0 | |
| 199 return 1 | |
| 200 | |
| 201 def listParents(self, elem, parentlist): | |
| 202 if elem.parent != None: | |
| 203 self.listParents(elem.parent, parentlist) | |
| 204 parentlist.append(elem.name) | |
| 205 | |
| 206 def isRootMatch(self, elem): | |
| 207 if (self.elementName == None or self.elementName == elem.name) and \ | |
| 208 self.matchesPredicates(elem): | |
| 209 if self.childLocation != None: | |
| 210 for c in elem.elements(): | |
| 211 if self.childLocation.matches(c): | |
| 212 return True | |
| 213 else: | |
| 214 return True | |
| 215 return False | |
| 216 | |
| 217 def findFirstRootMatch(self, elem): | |
| 218 if (self.elementName == None or self.elementName == elem.name) and \ | |
| 219 self.matchesPredicates(elem): | |
| 220 # Thus far, the name matches and the predicates match, | |
| 221 # now check into the children and find the first one | |
| 222 # that matches the rest of the structure | |
| 223 # the rest of the structure | |
| 224 if self.childLocation != None: | |
| 225 for c in elem.elements(): | |
| 226 if self.childLocation.matches(c): | |
| 227 return c | |
| 228 return None | |
| 229 else: | |
| 230 # No children locations; this is a match! | |
| 231 return elem | |
| 232 else: | |
| 233 # Ok, predicates or name didn't match, so we need to start | |
| 234 # down each child and treat it as the root and try | |
| 235 # again | |
| 236 for c in elem.elements(): | |
| 237 if self.matches(c): | |
| 238 return c | |
| 239 # No children matched... | |
| 240 return None | |
| 241 | |
| 242 def matches(self, elem): | |
| 243 if self.isRootMatch(elem): | |
| 244 return True | |
| 245 else: | |
| 246 # Ok, initial element isn't an exact match, walk | |
| 247 # down each child and treat it as the root and try | |
| 248 # again | |
| 249 for c in elem.elements(): | |
| 250 if self.matches(c): | |
| 251 return True | |
| 252 # No children matched... | |
| 253 return False | |
| 254 | |
| 255 def queryForString(self, elem, resultbuf): | |
| 256 raise NotImplementedError( | |
| 257 "queryForString is not implemented for any location") | |
| 258 | |
| 259 def queryForNodes(self, elem, resultlist): | |
| 260 # First check to see if _this_ element is a root | |
| 261 if self.isRootMatch(elem): | |
| 262 resultlist.append(elem) | |
| 263 | |
| 264 # Now check each child | |
| 265 for c in elem.elements(): | |
| 266 self.queryForNodes(c, resultlist) | |
| 267 | |
| 268 | |
| 269 def queryForStringList(self, elem, resultlist): | |
| 270 if self.isRootMatch(elem): | |
| 271 for c in elem.children: | |
| 272 if isinstance(c, (str, unicode)): | |
| 273 resultlist.append(c) | |
| 274 for c in elem.elements(): | |
| 275 self.queryForStringList(c, resultlist) | |
| 276 | |
| 277 | |
| 278 class XPathQuery: | |
| 279 def __init__(self, queryStr): | |
| 280 self.queryStr = queryStr | |
| 281 from twisted.words.xish.xpathparser import parse | |
| 282 self.baseLocation = parse('XPATH', queryStr) | |
| 283 | |
| 284 def __hash__(self): | |
| 285 return self.queryStr.__hash__() | |
| 286 | |
| 287 def matches(self, elem): | |
| 288 return self.baseLocation.matches(elem) | |
| 289 | |
| 290 def queryForString(self, elem): | |
| 291 result = StringIO.StringIO() | |
| 292 self.baseLocation.queryForString(elem, result) | |
| 293 return result.getvalue() | |
| 294 | |
| 295 def queryForNodes(self, elem): | |
| 296 result = [] | |
| 297 self.baseLocation.queryForNodes(elem, result) | |
| 298 if len(result) == 0: | |
| 299 return None | |
| 300 else: | |
| 301 return result | |
| 302 | |
| 303 def queryForStringList(self, elem): | |
| 304 result = [] | |
| 305 self.baseLocation.queryForStringList(elem, result) | |
| 306 if len(result) == 0: | |
| 307 return None | |
| 308 else: | |
| 309 return result | |
| 310 | |
| 311 | |
| 312 __internedQueries = {} | |
| 313 | |
| 314 def internQuery(queryString): | |
| 315 if queryString not in __internedQueries: | |
| 316 __internedQueries[queryString] = XPathQuery(queryString) | |
| 317 return __internedQueries[queryString] | |
| 318 | |
| 319 | |
| 320 def matches(xpathstr, elem): | |
| 321 return internQuery(xpathstr).matches(elem) | |
| 322 | |
| 323 | |
| 324 def queryForStringList(xpathstr, elem): | |
| 325 return internQuery(xpathstr).queryForStringList(elem) | |
| 326 | |
| 327 | |
| 328 def queryForString(xpathstr, elem): | |
| 329 return internQuery(xpathstr).queryForString(elem) | |
| 330 | |
| 331 | |
| 332 def queryForNodes(xpathstr, elem): | |
| 333 return internQuery(xpathstr).queryForNodes(elem) | |
| OLD | NEW |