1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 """Classes and global objects related to resolving U{XML
17 Namespaces<http://www.w3.org/TR/2006/REC-xml-names-20060816/index.html>}."""
18
19 import pyxb
20 import pyxb.utils.utility
21 from pyxb.namespace import archive, utility
22 import logging
23
24 _log = logging.getLogger(__name__)
27 """Mix-in indicating that this object may have references to unseen named components.
28
29 This class is mixed-in to those XMLSchema components that have a reference
30 to another component that is identified by a QName. Resolution of that
31 component may need to be delayed if the definition of the component has
32 not yet been read.
33 """
34
35
36 _TraceResolution = False
37
39 """Determine whether this named component is resolved.
40
41 Override this in the child class."""
42 raise NotImplementedError("_Resolvable_mixin.isResolved in %s"% (type(self).__name__,))
43
45 """Perform whatever steps are required to resolve this component.
46
47 Resolution is performed in the context of the namespace to which the
48 component belongs. Invoking this method may fail to complete the
49 resolution process if the component itself depends on unresolved
50 components. The sole caller of this should be
51 L{_NamespaceResolution_mixin.resolveDefinitions}.
52
53 This method is permitted (nay, encouraged) to raise an exception if
54 resolution requires interpreting a QName and the named component
55 cannot be found.
56
57 Override this in the child class. In the prefix, if L{isResolved} is
58 true, return right away. If something prevents you from completing
59 resolution, invoke L{self._queueForResolution()} (so it is retried
60 later) and immediately return self. Prior to leaving after successful
61 resolution discard any cached dom node by setting C{self.__domNode=None}.
62
63 @return: C{self}, whether or not resolution succeeds.
64 @raise pyxb.SchemaValidationError: if resolution requlres a reference to an unknown component
65 """
66 raise NotImplementedError("_Resolvable_mixin._resolve in %s"% (type(self).__name__,))
67
69 """Short-hand to requeue an object if the class implements _namespaceContext().
70 """
71 if (why is not None) and self._TraceResolution:
72 _log.info('Resolution delayed for %s: %s\n\tDepends on: %s', self, why, depends_on)
73 self._namespaceContext().queueForResolution(self, depends_on)
74
76 """Mix-in that aggregates those aspects of XMLNamespaces relevant to
77 resolving component references.
78 """
79
80
81
82 __importedNamespaces = None
83
84
85
86 __referencedNamespaces = None
87
88
89
90 __unresolvedComponents = None
91
92
93
94
95 __unresolvedDependents = None
96
106
113
118
122
127
129 """Return the set of namespaces which some schema imported while
130 processing with this namespace as target."""
131 return frozenset(self.__importedNamespaces)
132
137
139 """Return the set of namespaces which appear in namespace declarations
140 of schema with this namespace as target."""
141 return frozenset(self.__referencedNamespaces)
142
144 """Invoked to note that a component may have references that will need
145 to be resolved.
146
147 Newly created named components are often unresolved, as are components
148 which, in the course of resolution, are found to depend on another
149 unresolved component.
150
151 @param resolvable: An instance of L{_Resolvable_mixin} that is later to
152 be resolved.
153
154 @keyword depends_on: C{None}, or an instance of L{_Resolvable_mixin}
155 which C{resolvable} requires to be resolved in order to resolve
156 itself.
157
158 @return: C{resolvable}
159 """
160 assert isinstance(resolvable, _Resolvable_mixin)
161 if not resolvable.isResolved():
162 assert depends_on is None or isinstance(depends_on, _Resolvable_mixin)
163 self.__unresolvedComponents.append(resolvable)
164 if depends_on is not None and not depends_on.isResolved():
165 from pyxb.xmlschema import structures
166 assert isinstance(depends_on, _Resolvable_mixin)
167 assert isinstance(depends_on, structures._NamedComponent_mixin)
168 self.__unresolvedDependents.setdefault(resolvable, set()).add(depends_on)
169 return resolvable
170
172 """Return C{True} iff this namespace has not been resolved."""
173 return self.__unresolvedComponents is not None
174
193
195 """Loop until all references within the associated resolvable objects
196 have been resolved.
197
198 This method iterates through all components on the unresolved list,
199 invoking the _resolve method of each. If the component could not be
200 resolved in this pass, it iis placed back on the list for the next
201 iteration. If an iteration completes without resolving any of the
202 unresolved components, a pyxb.NotInNamespaceError exception is raised.
203
204 @note: Do not invoke this until all top-level definitions for the
205 namespace have been provided. The resolution routines are entitled to
206 raise a validation exception if a reference to an unrecognized
207 component is encountered.
208 """
209 if not self.needsResolution():
210 return True
211
212 while 0 < len(self.__unresolvedComponents):
213
214
215
216 unresolved = self.__unresolvedComponents
217
218 self.__unresolvedComponents = []
219 self.__unresolvedDependents = {}
220 for resolvable in unresolved:
221
222 resolvable._resolve()
223
224
225 assert resolvable.isResolved() or (resolvable in self.__unresolvedComponents), 'Lost resolvable %s' % (resolvable,)
226
227
228
229
230
231 if (resolvable.isResolved() and (resolvable._clones() is not None)):
232 assert False
233 if self.__unresolvedComponents == unresolved:
234 if allow_unresolved:
235 return False
236
237
238
239 failed_components = []
240 from pyxb.xmlschema import structures
241 for d in self.__unresolvedComponents:
242 if isinstance(d, structures._NamedComponent_mixin):
243 failed_components.append('%s named %s' % (d.__class__.__name__, d.name()))
244 else:
245 failed_components.append('Anonymous %s' % (d.__class__.__name__,))
246 raise pyxb.NotInNamespaceError('Infinite loop in resolution:\n %s' % ("\n ".join(failed_components),))
247
248
249
250 self.__unresolvedComponents = None
251 self.__unresolvedDependents = None
252
253
254
255
256
257
258 self._releaseNamespaceContexts()
259
260 return True
261
265
267 """Returns a map from unresolved components to sets of components that
268 must be resolved first."""
269 return self.__unresolvedDependents
270
272 """Resolve all components in the sibling_namespaces.
273
274 @param sibling_namespaces : A set of namespaces expected to be closed
275 under dependency."""
276
277 for ns in sibling_namespaces:
278 ns.configureCategories([archive.NamespaceArchive._AnonymousCategory()])
279 ns.validateComponentModel()
280
281 def __keyForCompare (dependency_map):
282 """Sort namespaces so dependencies get resolved first.
283
284 Uses the trick underlying functools.cmp_to_key(), but optimized for
285 this special case. The dependency map is incorporated into the class
286 definition by scope.
287 """
288 class K (object):
289 def __init__ (self, ns, *args):
290 self.__ns = ns
291
292
293
294 def __lt__ (self, other):
295 return ((self.__ns in dependency_map.get(other.__ns, set())) \
296 and not (other.__ns in dependency_map.get(self.__ns, set())))
297
298
299
300 def __eq__ (self, other):
301 return (self.__ns in dependency_map.get(other.__ns, set())) == (other.__ns in dependency_map.get(self.__ns, set()))
302
303
304 def __ne__ (self, other):
305 return not self.__eq__(other)
306 def __le__ (self, other):
307 return self.__lt__(other) or self.__eq__(other)
308 def __gt__ (self, other):
309 return other.__lt__(self.__ns)
310 def __ge__ (self, other):
311 return other.__lt__(self.__ns) or self.__eq__(other)
312 return K
313
314 need_resolved_set = set(sibling_namespaces)
315 dependency_map = {}
316 last_state = None
317 while need_resolved_set:
318 need_resolved_list = list(need_resolved_set)
319 if dependency_map:
320 need_resolved_list.sort(key=__keyForCompare(dependency_map))
321 need_resolved_set = set()
322 dependency_map = {}
323 for ns in need_resolved_list:
324 if not ns.needsResolution():
325 continue
326 if not ns.resolveDefinitions(allow_unresolved=True):
327 deps = dependency_map.setdefault(ns, set())
328 for (c, dcs) in ns._unresolvedDependents().iteritems():
329 for dc in dcs:
330 dns = dc.expandedName().namespace()
331 if dns != ns:
332 deps.add(dns)
333 _log.info('Holding incomplete resolution %s depending on: ', ns.uri(), u' ; '.join([ unicode(_dns) for _dns in deps ]))
334 need_resolved_set.add(ns)
335
336
337
338
339
340 state = []
341 for ns in need_resolved_set:
342 state.append( (ns, len(ns._unresolvedComponents())) )
343 state = tuple(state)
344 if last_state == state:
345 raise pyxb.LogicError('Unexpected external dependency in sibling namespaces: %s' % (u"\n ".join( [unicode(_ns) for _ns in need_resolved_set ]),))
346 last_state = state
347
348 -class NamespaceContext (object):
349 """Records information associated with namespaces at a DOM node.
350 """
351
352 - def __str__ (self):
353 rv = [ u'NamespaceContext ' ]
354 if self.defaultNamespace() is not None:
355 rv.extend([ '(defaultNamespace=', unicode(self.defaultNamespace()), ') '])
356 if self.targetNamespace() is not None:
357 rv.extend([ '(targetNamespace=', unicode(self.targetNamespace()), ') '])
358 rv.append("\n")
359 for (pfx, ns) in self.inScopeNamespaces().iteritems():
360 if pfx is not None:
361 rv.append(' xmlns:%s=%s' % (pfx, unicode(ns)))
362 return u''.join(rv)
363
364 __TargetNamespaceAttributes = { }
365 @classmethod
366 - def _AddTargetNamespaceAttribute (cls, expanded_name, attribute_name):
367 assert expanded_name is not None
368 cls.__TargetNamespaceAttributes[expanded_name] = attribute_name
369 @classmethod
370 - def _TargetNamespaceAttribute (cls, expanded_name):
371 return cls.__TargetNamespaceAttributes.get(expanded_name)
372
373
374
375 __pendingReferencedNamespaces = None
376
377 - def defaultNamespace (self):
378 """The default namespace in effect at this node. E.g., C{xmlns="URN:default"}."""
379 return self.__defaultNamespace
380 __defaultNamespace = None
381
382
383
384
385
386
387
388 __fallbackToTargetNamespace = False
389
390 - def targetNamespace (self):
391 """The target namespace in effect at this node. Usually from the
392 C{targetNamespace} attribute. If no namespace is specified for the
393 schema, an absent namespace was assigned upon creation and will be
394 returned."""
395 return self.__targetNamespace
396 __targetNamespace = None
397
399 """Map from prefix strings to L{Namespace} instances associated with those
400 prefixes. The prefix C{None} identifies the default namespace."""
401 return self.__inScopeNamespaces
402 __inScopeNamespaces = None
403
404 - def prefixForNamespace (self, namespace):
405 """Return a prefix associated with the given namespace in this
406 context, or None if the namespace is the default or is not in
407 scope."""
408 for (pfx, ns) in self.__inScopeNamespaces.iteritems():
409 if namespace == ns:
410 return pfx
411 return None
412
413 @classmethod
414 - def GetNodeContext (cls, node, **kw):
415 """Get the L{NamespaceContext} instance that was assigned to the node.
416
417 If none has been assigned and keyword parameters are present, create
418 one treating this as the root node and the keyword parameters as
419 configuration information (e.g., default_namespace).
420
421 @raise pyxb.LogicError: no context is available and the keywords
422 required to create one were not provided
423 """
424 try:
425 return node.__namespaceContext
426 except AttributeError:
427 return NamespaceContext(node, **kw)
428
429 - def setNodeContext (self, node):
431
432 - def processXMLNS (self, prefix, uri):
433 if not self.__mutableInScopeNamespaces:
434 self.__inScopeNamespaces = self.__inScopeNamespaces.copy()
435 self.__mutableInScopeNamespaces = True
436 if uri:
437 if prefix is None:
438 ns = self.__defaultNamespace = utility.NamespaceForURI(uri, create_if_missing=True)
439 self.__inScopeNamespaces[None] = self.__defaultNamespace
440 else:
441 ns = utility.NamespaceForURI(uri, create_if_missing=True)
442 self.__inScopeNamespaces[prefix] = ns
443
444
445
446
447
448 if self.__targetNamespace:
449 self.__targetNamespace._referenceNamespace(ns)
450 else:
451 self.__pendingReferencedNamespaces.add(ns)
452 else:
453
454
455
456
457
458 if prefix is not None:
459 raise pyxb.NamespaceError(self, 'Attempt to undefine non-default namespace %s' % (prefix,))
460 self.__inScopeNamespaces.pop(prefix, None)
461 self.__defaultNamespace = None
462
463 - def finalizeTargetNamespace (self, tns_uri=None, including_context=None):
464 if tns_uri is not None:
465 assert 0 < len(tns_uri)
466
467
468
469
470
471 self.__targetNamespace = utility.NamespaceForURI(tns_uri, create_if_missing=True)
472 elif self.__targetNamespace is None:
473 if including_context is not None:
474 self.__targetNamespace = including_context.targetNamespace()
475 self.__fallbackToTargetNamespace = True
476 elif tns_uri is None:
477 self.__targetNamespace = utility.CreateAbsentNamespace()
478 else:
479 self.__targetNamespace = utility.NamespaceForURI(tns_uri, create_if_missing=True)
480 if self.__pendingReferencedNamespaces is not None:
481 [ self.__targetNamespace._referenceNamespace(_ns) for _ns in self.__pendingReferencedNamespaces ]
482 self.__pendingReferencedNamespace = None
483 assert self.__targetNamespace is not None
484 if (not self.__fallbackToTargetNamespace) and self.__targetNamespace.isAbsentNamespace():
485 self.__fallbackToTargetNamespace = True
486
487 - def __init__ (self,
488 dom_node=None,
489 parent_context=None,
490 including_context=None,
491 recurse=True,
492 default_namespace=None,
493 target_namespace=None,
494 in_scope_namespaces=None,
495 expanded_name=None,
496 finalize_target_namespace=True):
497 """Determine the namespace context that should be associated with the
498 given node and, optionally, its element children.
499
500 @param dom_node: The DOM node
501 @type dom_node: C{xml.dom.Element}
502 @keyword parent_context: Optional value that specifies the context
503 associated with C{dom_node}'s parent node. If not provided, only the
504 C{xml} namespace is in scope.
505 @type parent_context: L{NamespaceContext}
506 @keyword recurse: If True (default), create namespace contexts for all
507 element children of C{dom_node}
508 @type recurse: C{bool}
509 @keyword default_namespace: Optional value to set as the default
510 namespace. Values from C{parent_context} would override this, as
511 would an C{xmlns} attribute in the C{dom_node}.
512 @type default_namespace: L{NamespaceContext}
513 @keyword target_namespace: Optional value to set as the target
514 namespace. Values from C{parent_context} would override this, as
515 would a C{targetNamespace} attribute in the C{dom_node}
516 @type target_namespace: L{NamespaceContext}
517 @keyword in_scope_namespaces: Optional value to set as the initial set
518 of in-scope namespaces. The always-present namespaces are added to
519 this if necessary.
520 @type in_scope_namespaces: C{dict} mapping C{string} to L{Namespace}.
521 """
522 from pyxb.namespace import builtin
523
524 if dom_node is not None:
525 try:
526 assert dom_node.__namespaceContext is None
527 except AttributeError:
528 pass
529 dom_node.__namespaceContext = self
530
531 self.__defaultNamespace = default_namespace
532 self.__targetNamespace = target_namespace
533 self.__inScopeNamespaces = builtin._UndeclaredNamespaceMap
534 self.__mutableInScopeNamespaces = False
535
536 if in_scope_namespaces is not None:
537 if parent_context is not None:
538 raise pyxb.LogicError('Cannot provide both parent_context and in_scope_namespaces')
539 self.__inScopeNamespaces = builtin._UndeclaredNamespaceMap.copy()
540 self.__inScopeNamespaces.update(in_scope_namespaces)
541 self.__mutableInScopeNamespaces = True
542
543 if parent_context is not None:
544 self.__inScopeNamespaces = parent_context.inScopeNamespaces()
545 self.__mutableInScopeNamespaces = False
546 self.__defaultNamespace = parent_context.defaultNamespace()
547 self.__targetNamespace = parent_context.targetNamespace()
548 self.__fallbackToTargetNamespace = parent_context.__fallbackToTargetNamespace
549
550 if self.__targetNamespace is None:
551 self.__pendingReferencedNamespaces = set()
552 attribute_map = {}
553 if dom_node is not None:
554 if expanded_name is None:
555 expanded_name = pyxb.namespace.ExpandedName(dom_node)
556 for ai in range(dom_node.attributes.length):
557 attr = dom_node.attributes.item(ai)
558 if builtin.XMLNamespaces.uri() == attr.namespaceURI:
559 prefix = attr.localName
560 if 'xmlns' == prefix:
561 prefix = None
562 self.processXMLNS(prefix, attr.value)
563 else:
564 if attr.namespaceURI is not None:
565 uri = utility.NamespaceForURI(attr.namespaceURI, create_if_missing=True)
566 key = pyxb.namespace.ExpandedName(uri, attr.localName)
567 else:
568 key = pyxb.namespace.ExpandedName(None, attr.localName)
569 attribute_map[key] = attr.value
570
571 if finalize_target_namespace:
572 tns_uri = None
573 tns_attr = self._TargetNamespaceAttribute(expanded_name)
574 if tns_attr is not None:
575 tns_uri = attribute_map.get(tns_attr)
576 self.finalizeTargetNamespace(tns_uri, including_context=including_context)
577
578
579
580
581 if (dom_node is not None) and recurse:
582 from xml.dom import Node
583 assert Node.ELEMENT_NODE == dom_node.nodeType
584 for cn in dom_node.childNodes:
585 if Node.ELEMENT_NODE == cn.nodeType:
586 NamespaceContext(cn, self, True)
587
588 - def interpretQName (self, name, namespace=None):
589 """Convert the provided name into an L{ExpandedName}, i.e. a tuple of
590 L{Namespace} and local name.
591
592 If the name includes a prefix, that prefix must map to an in-scope
593 namespace in this context. Absence of a prefix maps to
594 L{defaultNamespace()}, which must be provided (or defaults to the
595 target namespace, if that is absent).
596
597 @param name: A QName.
598 @type name: C{str} or C{unicode}
599 @param name: Optional namespace to use for unqualified names when
600 there is no default namespace. Note that a defined default namespace,
601 even if absent, supersedes this value.
602 @return: An L{ExpandedName} tuple: ( L{Namespace}, C{str} )
603 @raise pyxb.SchemaValidationError: The prefix is not in scope
604 @raise pyxb.SchemaValidationError: No prefix is given and the default namespace is absent
605 """
606 assert isinstance(name, (str, unicode))
607 if 0 <= name.find(':'):
608 (prefix, local_name) = name.split(':', 1)
609 assert self.inScopeNamespaces() is not None
610 namespace = self.inScopeNamespaces().get(prefix)
611 if namespace is None:
612 raise pyxb.SchemaValidationError('No namespace declared for QName %s prefix' % (name,))
613 else:
614 local_name = name
615
616 if self.defaultNamespace() is not None:
617 namespace = self.defaultNamespace()
618
619
620 if (namespace is None) and self.__fallbackToTargetNamespace:
621 namespace = self.targetNamespace()
622 if namespace is None:
623 raise pyxb.SchemaValidationError('QName %s with absent default namespace cannot be resolved' % (local_name,))
624
625
626
627 if (namespace != self.targetNamespace()):
628 namespace.validateComponentModel()
629 return pyxb.namespace.ExpandedName(namespace, local_name)
630
631 - def queueForResolution (self, component, depends_on=None):
632 """Forwards to L{queueForResolution()<Namespace.queueForResolution>} in L{targetNamespace()}."""
633 assert isinstance(component, _Resolvable_mixin)
634 return self.targetNamespace().queueForResolution(component, depends_on)
635
636
637
638
639