/ lib / lxml / nsclasses.pxi
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