1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 """Classes and global objects related to resolving U{XML
16 Namespaces<http://www.w3.org/TR/2006/REC-xml-names-20060816/index.html>}."""
17
18 import pyxb
19 import os
20 import fnmatch
21 import pyxb.utils.utility
22 import archive
23 import utility
26 """Mix-in indicating that this object may have references to unseen named components.
27
28 This class is mixed-in to those XMLSchema components that have a reference
29 to another component that is identified by a QName. Resolution of that
30 component may need to be delayed if the definition of the component has
31 not yet been read.
32 """
33
34
35 _TraceResolution = False
36
38 """Determine whether this named component is resolved.
39
40 Override this in the child class."""
41 raise pyxb.LogicError('Resolved check not implemented in %s' % (self.__class__,))
42
44 """Perform whatever steps are required to resolve this component.
45
46 Resolution is performed in the context of the namespace to which the
47 component belongs. Invoking this method may fail to complete the
48 resolution process if the component itself depends on unresolved
49 components. The sole caller of this should be
50 L{_NamespaceResolution_mixin.resolveDefinitions}.
51
52 This method is permitted (nay, encouraged) to raise an exception if
53 resolution requires interpreting a QName and the named component
54 cannot be found.
55
56 Override this in the child class. In the prefix, if L{isResolved} is
57 true, return right away. If something prevents you from completing
58 resolution, invoke L{self._queueForResolution()} (so it is retried
59 later) and immediately return self. Prior to leaving after successful
60 resolution discard any cached dom node by setting C{self.__domNode=None}.
61
62 @return: C{self}, whether or not resolution succeeds.
63 @raise pyxb.SchemaValidationError: if resolution requlres a reference to an unknown component
64 """
65 raise pyxb.LogicError('Resolution not implemented in %s' % (self.__class__,))
66
73
75 """Mix-in that aggregates those aspects of XMLNamespaces relevant to
76 resolving component references.
77 """
78
79
80
81 __importedNamespaces = None
82
83
84
85 __referencedNamespaces = None
86
87
88
89 __unresolvedComponents = None
90
99
106
111
115
120
122 """Return the set of namespaces which some schema imported while
123 processing with this namespace as target."""
124 return frozenset(self.__importedNamespaces)
125
130
140
142 """Invoked to note that a component may have references that will need
143 to be resolved.
144
145 Newly created named components are often unresolved, as are components
146 which, in the course of resolution, are found to depend on another
147 unresolved component.
148
149 The provided object must be an instance of _Resolvable_mixin. This
150 method returns the resolvable object.
151 """
152 assert isinstance(resolvable, _Resolvable_mixin)
153 if not resolvable.isResolved():
154 self.__unresolvedComponents.append(resolvable)
155 return resolvable
156
158 """Return C{True} iff this namespace has not been resolved."""
159 return self.__unresolvedComponents is not None
160
175
177 """Loop until all references within the associated resolvable objects
178 have been resolved.
179
180 This method iterates through all components on the unresolved list,
181 invoking the _resolve method of each. If the component could not be
182 resolved in this pass, it iis placed back on the list for the next
183 iteration. If an iteration completes without resolving any of the
184 unresolved components, a pyxb.NotInNamespaceError exception is raised.
185
186 @note: Do not invoke this until all top-level definitions for the
187 namespace have been provided. The resolution routines are entitled to
188 raise a validation exception if a reference to an unrecognized
189 component is encountered.
190 """
191 num_loops = 0
192 if not self.needsResolution():
193 return True
194
195 while 0 < len(self.__unresolvedComponents):
196
197
198
199 unresolved = self.__unresolvedComponents
200
201 num_loops += 1
202
203
204 self.__unresolvedComponents = []
205 for resolvable in unresolved:
206
207 resolvable._resolve()
208
209
210 assert resolvable.isResolved() or (resolvable in self.__unresolvedComponents), 'Lost resolvable %s' % (resolvable,)
211
212
213
214
215
216 if (resolvable.isResolved() and (resolvable._clones() is not None)):
217 assert False
218 if self.__unresolvedComponents == unresolved:
219 if allow_unresolved:
220 return False
221
222
223
224 failed_components = []
225 import pyxb.xmlschema.structures
226 for d in self.__unresolvedComponents:
227 if isinstance(d, pyxb.xmlschema.structures._NamedComponent_mixin):
228 failed_components.append('%s named %s' % (d.__class__.__name__, d.name()))
229 else:
230 if isinstance(d, pyxb.xmlschema.structures.AttributeUse):
231 print d.attributeDeclaration()
232 failed_components.append('Anonymous %s' % (d.__class__.__name__,))
233 raise pyxb.NotInNamespaceError('Infinite loop in resolution:\n %s' % ("\n ".join(failed_components),))
234
235
236
237 self.__unresolvedComponents = None
238
239
240
241
242
243
244 self._releaseNamespaceContexts()
245
246 return True
247
251
270
271 -class NamespaceContext (object):
272 """Records information associated with namespaces at a DOM node.
273 """
274
275 - def __str__ (self):
276 rv = [ 'NamespaceContext ' ]
277 if self.defaultNamespace() is not None:
278 rv.extend([ '(defaultNamespace=', str(self.defaultNamespace()), ') '])
279 if self.targetNamespace() is not None:
280 rv.extend([ '(targetNamespace=', str(self.targetNamespace()), ') '])
281 rv.append("\n")
282 for (pfx, ns) in self.inScopeNamespaces().items():
283 if pfx is not None:
284 rv.append(' xmlns:%s=%s' % (pfx, str(ns)))
285 return ''.join(rv)
286
287 __TargetNamespaceAttributes = { }
288 @classmethod
289 - def _AddTargetNamespaceAttribute (cls, expanded_name, attribute_name):
292 @classmethod
293 - def _TargetNamespaceAttribute (cls, expanded_name):
295
296
297
298 __pendingReferencedNamespaces = None
299
300 - def defaultNamespace (self):
301 """The default namespace in effect at this node. E.g., C{xmlns="URN:default"}."""
302 return self.__defaultNamespace
303 __defaultNamespace = None
304
305
306
307
308
309
310
311 __fallbackToTargetNamespace = False
312
313 - def targetNamespace (self):
314 """The target namespace in effect at this node. Usually from the
315 C{targetNamespace} attribute. If no namespace is specified for the
316 schema, an absent namespace was assigned upon creation and will be
317 returned."""
318 return self.__targetNamespace
319 __targetNamespace = None
320
322 """Map from prefix strings to L{Namespace} instances associated with those
323 prefixes. The prefix C{None} identifies the default namespace."""
324 return self.__inScopeNamespaces
325 __inScopeNamespaces = None
326
327 - def prefixForNamespace (self, namespace):
328 """Return a prefix associated with the given namespace in this
329 context, or None if the namespace is the default or is not in
330 scope."""
331 for (pfx, ns) in self.__inScopeNamespaces.items():
332 if namespace == ns:
333 return pfx
334 return None
335
336 @classmethod
337 - def GetNodeContext (cls, node, **kw):
338 """Get the L{NamespaceContext} instance that was assigned to the node.
339
340 If none has been assigned and keyword parameters are present, create
341 one treating this as the root node and the keyword parameters as
342 configuration information (e.g., default_namespace).
343
344 @raise pyxb.LogicError: no context is available and the keywords
345 required to create one were not provided
346 """
347 try:
348 return node.__namespaceContext
349 except AttributeError:
350 return NamespaceContext(node, **kw)
351
352 - def setNodeContext (self, node):
354
355 - def processXMLNS (self, prefix, uri):
356 if not self.__mutableInScopeNamespaces:
357 self.__inScopeNamespaces = self.__inScopeNamespaces.copy()
358 self.__mutableInScopeNamespaces = True
359 if uri:
360 if prefix is None:
361 ns = self.__defaultNamespace = utility.NamespaceForURI(uri, create_if_missing=True)
362 self.__inScopeNamespaces[None] = self.__defaultNamespace
363 else:
364 ns = utility.NamespaceForURI(uri, create_if_missing=True)
365 self.__inScopeNamespaces[prefix] = ns
366
367
368
369
370
371 if self.__targetNamespace:
372 self.__targetNamespace._referenceNamespace(ns)
373 else:
374 self.__pendingReferencedNamespaces.add(ns)
375 else:
376
377
378
379
380
381 if prefix is not None:
382 raise pyxb.NamespaceError(self, 'Attempt to undefine non-default namespace %s' % (attr.localName,))
383 self.__inScopeNamespaces.pop(prefix, None)
384 self.__defaultNamespace = None
385
386 - def finalizeTargetNamespace (self, tns_uri=None, including_context=None):
387 if tns_uri is not None:
388 assert 0 < len(tns_uri)
389
390
391
392
393
394 self.__targetNamespace = utility.NamespaceForURI(tns_uri, create_if_missing=True)
395 elif self.__targetNamespace is None:
396 if including_context is not None:
397 self.__targetNamespace = including_context.targetNamespace()
398 self.__fallbackToTargetNamespace = True
399 elif tns_uri is None:
400 self.__targetNamespace = utility.CreateAbsentNamespace()
401 else:
402 self.__targetNamespace = utility.NamespaceForURI(tns_uri, create_if_missing=True)
403 if self.__pendingReferencedNamespaces is not None:
404 [ self.__targetNamespace._referenceNamespace(_ns) for _ns in self.__pendingReferencedNamespaces ]
405 self.__pendingReferencedNamespace = None
406 assert self.__targetNamespace is not None
407 if (not self.__fallbackToTargetNamespace) and self.__targetNamespace.isAbsentNamespace():
408 self.__fallbackToTargetNamespace = True
409
410 - def __init__ (self,
411 dom_node=None,
412 parent_context=None,
413 including_context=None,
414 recurse=True,
415 default_namespace=None,
416 target_namespace=None,
417 in_scope_namespaces=None,
418 expanded_name=None,
419 finalize_target_namespace=True):
420 """Determine the namespace context that should be associated with the
421 given node and, optionally, its element children.
422
423 @param dom_node: The DOM node
424 @type dom_node: C{xml.dom.Element}
425 @keyword parent_context: Optional value that specifies the context
426 associated with C{dom_node}'s parent node. If not provided, only the
427 C{xml} namespace is in scope.
428 @type parent_context: L{NamespaceContext}
429 @keyword recurse: If True (default), create namespace contexts for all
430 element children of C{dom_node}
431 @type recurse: C{bool}
432 @keyword default_namespace: Optional value to set as the default
433 namespace. Values from C{parent_context} would override this, as
434 would an C{xmlns} attribute in the C{dom_node}.
435 @type default_namespace: L{NamespaceContext}
436 @keyword target_namespace: Optional value to set as the target
437 namespace. Values from C{parent_context} would override this, as
438 would a C{targetNamespace} attribute in the C{dom_node}
439 @type target_namespace: L{NamespaceContext}
440 @keyword in_scope_namespaces: Optional value to set as the initial set
441 of in-scope namespaces. The always-present namespaces are added to
442 this if necessary.
443 @type in_scope_namespaces: C{dict} mapping C{string} to L{Namespace}.
444 """
445 import builtin
446
447 if dom_node is not None:
448 try:
449 assert dom_node.__namespaceContext is None
450 except AttributeError:
451 pass
452 dom_node.__namespaceContext = self
453
454 self.__defaultNamespace = default_namespace
455 self.__targetNamespace = target_namespace
456 self.__inScopeNamespaces = builtin._UndeclaredNamespaceMap
457 self.__mutableInScopeNamespaces = False
458
459 if in_scope_namespaces is not None:
460 if parent_context is not None:
461 raise LogicError('Cannot provide both parent_context and in_scope_namespaces')
462 self.__inScopeNamespaces = builtin._UndeclaredNamespaceMap.copy()
463 self.__inScopeNamespaces.update(in_scope_namespaces)
464 self.__mutableInScopeNamespaces = True
465
466 if parent_context is not None:
467 self.__inScopeNamespaces = parent_context.inScopeNamespaces()
468 self.__mutableInScopeNamespaces = False
469 self.__defaultNamespace = parent_context.defaultNamespace()
470 self.__targetNamespace = parent_context.targetNamespace()
471 self.__fallbackToTargetNamespace = parent_context.__fallbackToTargetNamespace
472
473 if self.__targetNamespace is None:
474 self.__pendingReferencedNamespaces = set()
475 attribute_map = {}
476 if dom_node is not None:
477 if expanded_name is None:
478 expanded_name = pyxb.namespace.ExpandedName(dom_node)
479 for ai in range(dom_node.attributes.length):
480 attr = dom_node.attributes.item(ai)
481 if builtin.XMLNamespaces.uri() == attr.namespaceURI:
482 prefix = attr.localName
483 if 'xmlns' == prefix:
484 prefix = None
485 self.processXMLNS(prefix, attr.value)
486 else:
487 if attr.namespaceURI is not None:
488 uri = utility.NamespaceForURI(attr.namespaceURI, create_if_missing=True)
489 key = pyxb.namespace.ExpandedName(uri, attr.localName)
490 else:
491 key = pyxb.namespace.ExpandedName(None, attr.localName)
492 attribute_map[key] = attr.value
493
494 if finalize_target_namespace:
495 tns_uri = None
496 tns_attr = self._TargetNamespaceAttribute(expanded_name)
497 if tns_attr is not None:
498 tns_uri = attribute_map.get(tns_attr)
499 self.finalizeTargetNamespace(tns_uri, including_context=including_context)
500
501
502
503
504 if (dom_node is not None) and recurse:
505 from xml.dom import Node
506 assert Node.ELEMENT_NODE == dom_node.nodeType
507 for cn in dom_node.childNodes:
508 if Node.ELEMENT_NODE == cn.nodeType:
509 NamespaceContext(cn, self, True)
510
511 - def interpretQName (self, name, namespace=None):
512 """Convert the provided name into an L{ExpandedName}, i.e. a tuple of
513 L{Namespace} and local name.
514
515 If the name includes a prefix, that prefix must map to an in-scope
516 namespace in this context. Absence of a prefix maps to
517 L{defaultNamespace()}, which must be provided (or defaults to the
518 target namespace, if that is absent).
519
520 @param name: A QName.
521 @type name: C{str} or C{unicode}
522 @param name: Optional namespace to use for unqualified names when
523 there is no default namespace. Note that a defined default namespace,
524 even if absent, supersedes this value.
525 @return: An L{ExpandedName} tuple: ( L{Namespace}, C{str} )
526 @raise pyxb.SchemaValidationError: The prefix is not in scope
527 @raise pyxb.SchemaValidationError: No prefix is given and the default namespace is absent
528 """
529 assert isinstance(name, (str, unicode))
530 if 0 <= name.find(':'):
531 (prefix, local_name) = name.split(':', 1)
532 assert self.inScopeNamespaces() is not None
533 namespace = self.inScopeNamespaces().get(prefix)
534 if namespace is None:
535 raise pyxb.SchemaValidationError('No namespace declared for QName %s prefix' % (name,))
536 else:
537 local_name = name
538
539 if self.defaultNamespace() is not None:
540 namespace = self.defaultNamespace()
541
542
543 if (namespace is None) and self.__fallbackToTargetNamespace:
544 namespace = self.targetNamespace()
545 if namespace is None:
546 raise pyxb.SchemaValidationError('QName %s with absent default namespace cannot be resolved' % (local_name,))
547
548
549
550 if (namespace != self.targetNamespace()):
551 namespace.validateComponentModel()
552 return pyxb.namespace.ExpandedName(namespace, local_name)
553
554 - def queueForResolution (self, component):
555 """Forwards to L{queueForResolution()<Namespace.queueForResolution>} in L{targetNamespace()}."""
556 assert isinstance(component, _Resolvable_mixin)
557 return self.targetNamespace().queueForResolution(component)
558
559
560
561
562