nsclasses.pxi
1 # module-level API for namespace implementations 2 3 cdef class LxmlRegistryError(LxmlError): 4 """Base class of lxml registry errors. 5 """ 6 7 cdef class NamespaceRegistryError(LxmlRegistryError): 8 """Error registering a namespace extension. 9 """ 10 11 12 @cython.internal 13 cdef class _NamespaceRegistry: 14 u"Dictionary-like namespace registry" 15 cdef object _ns_uri 16 cdef bytes _ns_uri_utf 17 cdef dict _entries 18 cdef char* _c_ns_uri_utf 19 def __cinit__(self, ns_uri): 20 self._ns_uri = ns_uri 21 if ns_uri is None: 22 self._ns_uri_utf = None 23 self._c_ns_uri_utf = NULL 24 else: 25 self._ns_uri_utf = _utf8(ns_uri) 26 self._c_ns_uri_utf = _cstr(self._ns_uri_utf) 27 self._entries = {} 28 29 def update(self, class_dict_iterable): 30 u"""update(self, class_dict_iterable) 31 32 Forgivingly update the registry. 33 34 ``class_dict_iterable`` may be a dict or some other iterable 35 that yields (name, value) pairs. 36 37 If a value does not match the required type for this registry, 38 or if the name starts with '_', it will be silently discarded. 39 This allows registrations at the module or class level using 40 vars(), globals() etc.""" 41 if hasattr(class_dict_iterable, u'items'): 42 class_dict_iterable = class_dict_iterable.items() 43 for name, item in class_dict_iterable: 44 if (name is None or name[:1] != '_') and callable(item): 45 self[name] = item 46 47 def __getitem__(self, name): 48 if name is not None: 49 name = _utf8(name) 50 return self._get(name) 51 52 def __delitem__(self, name): 53 if name is not None: 54 name = _utf8(name) 55 del self._entries[name] 56 57 cdef object _get(self, object name): 58 cdef python.PyObject* dict_result 59 dict_result = python.PyDict_GetItem(self._entries, name) 60 if dict_result is NULL: 61 raise KeyError, u"Name not registered." 62 return <object>dict_result 63 64 cdef object _getForString(self, char* name): 65 cdef python.PyObject* dict_result 66 dict_result = python.PyDict_GetItem(self._entries, name) 67 if dict_result is NULL: 68 raise KeyError, u"Name not registered." 69 return <object>dict_result 70 71 def __iter__(self): 72 return iter(self._entries) 73 74 def items(self): 75 return list(self._entries.items()) 76 77 def iteritems(self): 78 return iter(self._entries.items()) 79 80 def clear(self): 81 self._entries.clear() 82 83 def __call__(self, obj): 84 # Usage as decorator: 85 # ns = lookup.get_namespace("...") 86 # @ns('abc') 87 # class element(ElementBase): pass 88 # 89 # @ns 90 # class elementname(ElementBase): pass 91 92 if obj is None or python._isString(obj): 93 # @ns(None) or @ns('tag') 94 return partial(self.__deco, obj) 95 # plain @ns decorator 96 self[obj.__name__] = obj 97 return obj 98 99 def __deco(self, name, obj): 100 self[name] = obj 101 return obj 102 103 104 @cython.final 105 @cython.internal 106 cdef class _ClassNamespaceRegistry(_NamespaceRegistry): 107 u"Dictionary-like registry for namespace implementation classes" 108 def __setitem__(self, name, item): 109 if not isinstance(item, type) or not issubclass(item, ElementBase): 110 raise NamespaceRegistryError, \ 111 u"Registered element classes must be subtypes of ElementBase" 112 if name is not None: 113 name = _utf8(name) 114 self._entries[name] = item 115 116 def __repr__(self): 117 return u"Namespace(%r)" % self._ns_uri 118 119 120 cdef class ElementNamespaceClassLookup(FallbackElementClassLookup): 121 u"""ElementNamespaceClassLookup(self, fallback=None) 122 123 Element class lookup scheme that searches the Element class in the 124 Namespace registry. 125 126 Usage: 127 128 >>> lookup = ElementNamespaceClassLookup() 129 >>> ns_elements = lookup.get_namespace("http://schema.org/Movie") 130 131 >>> @ns_elements 132 ... class movie(ElementBase): 133 ... "Element implementation for 'movie' tag (using class name) in schema namespace." 134 135 >>> @ns_elements("movie") 136 ... class MovieElement(ElementBase): 137 ... "Element implementation for 'movie' tag (explicit tag name) in schema namespace." 138 """ 139 cdef dict _namespace_registries 140 def __cinit__(self): 141 self._namespace_registries = {} 142 143 def __init__(self, ElementClassLookup fallback=None): 144 FallbackElementClassLookup.__init__(self, fallback) 145 self._lookup_function = _find_nselement_class 146 147 def get_namespace(self, ns_uri): 148 u"""get_namespace(self, ns_uri) 149 150 Retrieve the namespace object associated with the given URI. 151 Pass None for the empty namespace. 152 153 Creates a new namespace object if it does not yet exist.""" 154 if ns_uri: 155 ns_utf = _utf8(ns_uri) 156 else: 157 ns_utf = None 158 try: 159 return self._namespace_registries[ns_utf] 160 except KeyError: 161 registry = self._namespace_registries[ns_utf] = \ 162 _ClassNamespaceRegistry(ns_uri) 163 return registry 164 165 cdef object _find_nselement_class(state, _Document doc, xmlNode* c_node): 166 cdef python.PyObject* dict_result 167 cdef ElementNamespaceClassLookup lookup 168 cdef _NamespaceRegistry registry 169 if state is None: 170 return _lookupDefaultElementClass(None, doc, c_node) 171 172 lookup = <ElementNamespaceClassLookup>state 173 if c_node.type != tree.XML_ELEMENT_NODE: 174 return _callLookupFallback(lookup, doc, c_node) 175 176 c_namespace_utf = _getNs(c_node) 177 if c_namespace_utf is not NULL: 178 dict_result = python.PyDict_GetItem( 179 lookup._namespace_registries, <unsigned char*>c_namespace_utf) 180 else: 181 dict_result = python.PyDict_GetItem( 182 lookup._namespace_registries, None) 183 if dict_result is not NULL: 184 registry = <_NamespaceRegistry>dict_result 185 classes = registry._entries 186 187 if c_node.name is not NULL: 188 dict_result = python.PyDict_GetItem( 189 classes, <unsigned char*>c_node.name) 190 else: 191 dict_result = NULL 192 193 if dict_result is NULL: 194 dict_result = python.PyDict_GetItem(classes, None) 195 196 if dict_result is not NULL: 197 return <object>dict_result 198 return _callLookupFallback(lookup, doc, c_node) 199 200 201 ################################################################################ 202 # XPath extension functions 203 204 cdef dict __FUNCTION_NAMESPACE_REGISTRIES 205 __FUNCTION_NAMESPACE_REGISTRIES = {} 206 207 def FunctionNamespace(ns_uri): 208 u"""FunctionNamespace(ns_uri) 209 210 Retrieve the function namespace object associated with the given 211 URI. 212 213 Creates a new one if it does not yet exist. A function namespace 214 can only be used to register extension functions. 215 216 Usage: 217 218 >>> ns_functions = FunctionNamespace("http://schema.org/Movie") 219 220 >>> @ns_functions # uses function name 221 ... def add2(x): 222 ... return x + 2 223 224 >>> @ns_functions("add3") # uses explicit name 225 ... def add_three(x): 226 ... return x + 3 227 """ 228 ns_utf = _utf8(ns_uri) if ns_uri else None 229 try: 230 return __FUNCTION_NAMESPACE_REGISTRIES[ns_utf] 231 except KeyError: 232 registry = __FUNCTION_NAMESPACE_REGISTRIES[ns_utf] = \ 233 _XPathFunctionNamespaceRegistry(ns_uri) 234 return registry 235 236 @cython.internal 237 cdef class _FunctionNamespaceRegistry(_NamespaceRegistry): 238 def __setitem__(self, name, item): 239 if not callable(item): 240 raise NamespaceRegistryError, \ 241 u"Registered functions must be callable." 242 if not name: 243 raise ValueError, \ 244 u"extensions must have non empty names" 245 self._entries[_utf8(name)] = item 246 247 def __repr__(self): 248 return u"FunctionNamespace(%r)" % self._ns_uri 249 250 @cython.final 251 @cython.internal 252 cdef class _XPathFunctionNamespaceRegistry(_FunctionNamespaceRegistry): 253 cdef object _prefix 254 cdef bytes _prefix_utf 255 256 property prefix: 257 u"Namespace prefix for extension functions." 258 def __del__(self): 259 self._prefix = None # no prefix configured 260 self._prefix_utf = None 261 def __get__(self): 262 if self._prefix is None: 263 return '' 264 else: 265 return self._prefix 266 def __set__(self, prefix): 267 if prefix == '': 268 prefix = None # empty prefix 269 self._prefix_utf = _utf8(prefix) if prefix is not None else None 270 self._prefix = prefix 271 272 cdef list _find_all_extension_prefixes(): 273 u"Internal lookup function to find all function prefixes for XSLT/XPath." 274 cdef _XPathFunctionNamespaceRegistry registry 275 cdef list ns_prefixes = [] 276 for registry in __FUNCTION_NAMESPACE_REGISTRIES.itervalues(): 277 if registry._prefix_utf is not None: 278 if registry._ns_uri_utf is not None: 279 ns_prefixes.append( 280 (registry._prefix_utf, registry._ns_uri_utf)) 281 return ns_prefixes