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 pyxb.LogicError('Resolved check not implemented in %s' % (self.__class__,))
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 pyxb.LogicError('Resolution not implemented in %s' % (self.__class__,))
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 cmp_for_deps (ns1, ns2):
282 """Sort namespaces so dependencies get resolved first"""
283 if ns2 not in dependency_map.get(ns1, set()):
284 return -1
285 if ns1 not in dependency_map.get(ns2, set()):
286 return 1
287 return 0
288
289 need_resolved_set = set(sibling_namespaces)
290 dependency_map = {}
291 last_state = None
292 while need_resolved_set:
293 need_resolved_list = list(need_resolved_set)
294 if dependency_map:
295 need_resolved_list.sort(cmp_for_deps)
296 need_resolved_set = set()
297 dependency_map = {}
298 for ns in need_resolved_list:
299 if not ns.needsResolution():
300 continue
301 if not ns.resolveDefinitions(allow_unresolved=True):
302 deps = dependency_map.setdefault(ns, set())
303 for (c, dcs) in ns._unresolvedDependents().iteritems():
304 for dc in dcs:
305 dns = dc.expandedName().namespace()
306 if dns != ns:
307 deps.add(dns)
308 _log.info('Holding incomplete resolution %s depending on: ', ns.uri(), ' ; '.join([ str(_dns) for _dns in deps ]))
309 need_resolved_set.add(ns)
310
311
312
313
314
315 state = []
316 for ns in need_resolved_set:
317 state.append( (ns, len(ns._unresolvedComponents())) )
318 state = tuple(state)
319 if last_state == state:
320 raise pyxb.LogicError('Unexpected external dependency in sibling namespaces: %s' % ("\n ".join( [str(_ns) for _ns in need_resolved_set ]),))
321 last_state = state
322
323 -class NamespaceContext (object):
324 """Records information associated with namespaces at a DOM node.
325 """
326
327 - def __str__ (self):
328 rv = [ 'NamespaceContext ' ]
329 if self.defaultNamespace() is not None:
330 rv.extend([ '(defaultNamespace=', str(self.defaultNamespace()), ') '])
331 if self.targetNamespace() is not None:
332 rv.extend([ '(targetNamespace=', str(self.targetNamespace()), ') '])
333 rv.append("\n")
334 for (pfx, ns) in self.inScopeNamespaces().items():
335 if pfx is not None:
336 rv.append(' xmlns:%s=%s' % (pfx, str(ns)))
337 return ''.join(rv)
338
339 __TargetNamespaceAttributes = { }
340 @classmethod
341 - def _AddTargetNamespaceAttribute (cls, expanded_name, attribute_name):
344 @classmethod
345 - def _TargetNamespaceAttribute (cls, expanded_name):
347
348
349
350 __pendingReferencedNamespaces = None
351
352 - def defaultNamespace (self):
353 """The default namespace in effect at this node. E.g., C{xmlns="URN:default"}."""
354 return self.__defaultNamespace
355 __defaultNamespace = None
356
357
358
359
360
361
362
363 __fallbackToTargetNamespace = False
364
365 - def targetNamespace (self):
366 """The target namespace in effect at this node. Usually from the
367 C{targetNamespace} attribute. If no namespace is specified for the
368 schema, an absent namespace was assigned upon creation and will be
369 returned."""
370 return self.__targetNamespace
371 __targetNamespace = None
372
374 """Map from prefix strings to L{Namespace} instances associated with those
375 prefixes. The prefix C{None} identifies the default namespace."""
376 return self.__inScopeNamespaces
377 __inScopeNamespaces = None
378
379 - def prefixForNamespace (self, namespace):
380 """Return a prefix associated with the given namespace in this
381 context, or None if the namespace is the default or is not in
382 scope."""
383 for (pfx, ns) in self.__inScopeNamespaces.items():
384 if namespace == ns:
385 return pfx
386 return None
387
388 @classmethod
389 - def GetNodeContext (cls, node, **kw):
390 """Get the L{NamespaceContext} instance that was assigned to the node.
391
392 If none has been assigned and keyword parameters are present, create
393 one treating this as the root node and the keyword parameters as
394 configuration information (e.g., default_namespace).
395
396 @raise pyxb.LogicError: no context is available and the keywords
397 required to create one were not provided
398 """
399 try:
400 return node.__namespaceContext
401 except AttributeError:
402 return NamespaceContext(node, **kw)
403
404 - def setNodeContext (self, node):
406
407 - def processXMLNS (self, prefix, uri):
408 if not self.__mutableInScopeNamespaces:
409 self.__inScopeNamespaces = self.__inScopeNamespaces.copy()
410 self.__mutableInScopeNamespaces = True
411 if uri:
412 if prefix is None:
413 ns = self.__defaultNamespace = utility.NamespaceForURI(uri, create_if_missing=True)
414 self.__inScopeNamespaces[None] = self.__defaultNamespace
415 else:
416 ns = utility.NamespaceForURI(uri, create_if_missing=True)
417 self.__inScopeNamespaces[prefix] = ns
418
419
420
421
422
423 if self.__targetNamespace:
424 self.__targetNamespace._referenceNamespace(ns)
425 else:
426 self.__pendingReferencedNamespaces.add(ns)
427 else:
428
429
430
431
432
433 if prefix is not None:
434 raise pyxb.NamespaceError(self, 'Attempt to undefine non-default namespace %s' % (prefix,))
435 self.__inScopeNamespaces.pop(prefix, None)
436 self.__defaultNamespace = None
437
438 - def finalizeTargetNamespace (self, tns_uri=None, including_context=None):
439 if tns_uri is not None:
440 assert 0 < len(tns_uri)
441
442
443
444
445
446 self.__targetNamespace = utility.NamespaceForURI(tns_uri, create_if_missing=True)
447 elif self.__targetNamespace is None:
448 if including_context is not None:
449 self.__targetNamespace = including_context.targetNamespace()
450 self.__fallbackToTargetNamespace = True
451 elif tns_uri is None:
452 self.__targetNamespace = utility.CreateAbsentNamespace()
453 else:
454 self.__targetNamespace = utility.NamespaceForURI(tns_uri, create_if_missing=True)
455 if self.__pendingReferencedNamespaces is not None:
456 [ self.__targetNamespace._referenceNamespace(_ns) for _ns in self.__pendingReferencedNamespaces ]
457 self.__pendingReferencedNamespace = None
458 assert self.__targetNamespace is not None
459 if (not self.__fallbackToTargetNamespace) and self.__targetNamespace.isAbsentNamespace():
460 self.__fallbackToTargetNamespace = True
461
462 - def __init__ (self,
463 dom_node=None,
464 parent_context=None,
465 including_context=None,
466 recurse=True,
467 default_namespace=None,
468 target_namespace=None,
469 in_scope_namespaces=None,
470 expanded_name=None,
471 finalize_target_namespace=True):
472 """Determine the namespace context that should be associated with the
473 given node and, optionally, its element children.
474
475 @param dom_node: The DOM node
476 @type dom_node: C{xml.dom.Element}
477 @keyword parent_context: Optional value that specifies the context
478 associated with C{dom_node}'s parent node. If not provided, only the
479 C{xml} namespace is in scope.
480 @type parent_context: L{NamespaceContext}
481 @keyword recurse: If True (default), create namespace contexts for all
482 element children of C{dom_node}
483 @type recurse: C{bool}
484 @keyword default_namespace: Optional value to set as the default
485 namespace. Values from C{parent_context} would override this, as
486 would an C{xmlns} attribute in the C{dom_node}.
487 @type default_namespace: L{NamespaceContext}
488 @keyword target_namespace: Optional value to set as the target
489 namespace. Values from C{parent_context} would override this, as
490 would a C{targetNamespace} attribute in the C{dom_node}
491 @type target_namespace: L{NamespaceContext}
492 @keyword in_scope_namespaces: Optional value to set as the initial set
493 of in-scope namespaces. The always-present namespaces are added to
494 this if necessary.
495 @type in_scope_namespaces: C{dict} mapping C{string} to L{Namespace}.
496 """
497 from pyxb.namespace import builtin
498
499 if dom_node is not None:
500 try:
501 assert dom_node.__namespaceContext is None
502 except AttributeError:
503 pass
504 dom_node.__namespaceContext = self
505
506 self.__defaultNamespace = default_namespace
507 self.__targetNamespace = target_namespace
508 self.__inScopeNamespaces = builtin._UndeclaredNamespaceMap
509 self.__mutableInScopeNamespaces = False
510
511 if in_scope_namespaces is not None:
512 if parent_context is not None:
513 raise pyxb.LogicError('Cannot provide both parent_context and in_scope_namespaces')
514 self.__inScopeNamespaces = builtin._UndeclaredNamespaceMap.copy()
515 self.__inScopeNamespaces.update(in_scope_namespaces)
516 self.__mutableInScopeNamespaces = True
517
518 if parent_context is not None:
519 self.__inScopeNamespaces = parent_context.inScopeNamespaces()
520 self.__mutableInScopeNamespaces = False
521 self.__defaultNamespace = parent_context.defaultNamespace()
522 self.__targetNamespace = parent_context.targetNamespace()
523 self.__fallbackToTargetNamespace = parent_context.__fallbackToTargetNamespace
524
525 if self.__targetNamespace is None:
526 self.__pendingReferencedNamespaces = set()
527 attribute_map = {}
528 if dom_node is not None:
529 if expanded_name is None:
530 expanded_name = pyxb.namespace.ExpandedName(dom_node)
531 for ai in range(dom_node.attributes.length):
532 attr = dom_node.attributes.item(ai)
533 if builtin.XMLNamespaces.uri() == attr.namespaceURI:
534 prefix = attr.localName
535 if 'xmlns' == prefix:
536 prefix = None
537 self.processXMLNS(prefix, attr.value)
538 else:
539 if attr.namespaceURI is not None:
540 uri = utility.NamespaceForURI(attr.namespaceURI, create_if_missing=True)
541 key = pyxb.namespace.ExpandedName(uri, attr.localName)
542 else:
543 key = pyxb.namespace.ExpandedName(None, attr.localName)
544 attribute_map[key] = attr.value
545
546 if finalize_target_namespace:
547 tns_uri = None
548 tns_attr = self._TargetNamespaceAttribute(expanded_name)
549 if tns_attr is not None:
550 tns_uri = attribute_map.get(tns_attr)
551 self.finalizeTargetNamespace(tns_uri, including_context=including_context)
552
553
554
555
556 if (dom_node is not None) and recurse:
557 from xml.dom import Node
558 assert Node.ELEMENT_NODE == dom_node.nodeType
559 for cn in dom_node.childNodes:
560 if Node.ELEMENT_NODE == cn.nodeType:
561 NamespaceContext(cn, self, True)
562
563 - def interpretQName (self, name, namespace=None):
564 """Convert the provided name into an L{ExpandedName}, i.e. a tuple of
565 L{Namespace} and local name.
566
567 If the name includes a prefix, that prefix must map to an in-scope
568 namespace in this context. Absence of a prefix maps to
569 L{defaultNamespace()}, which must be provided (or defaults to the
570 target namespace, if that is absent).
571
572 @param name: A QName.
573 @type name: C{str} or C{unicode}
574 @param name: Optional namespace to use for unqualified names when
575 there is no default namespace. Note that a defined default namespace,
576 even if absent, supersedes this value.
577 @return: An L{ExpandedName} tuple: ( L{Namespace}, C{str} )
578 @raise pyxb.SchemaValidationError: The prefix is not in scope
579 @raise pyxb.SchemaValidationError: No prefix is given and the default namespace is absent
580 """
581 assert isinstance(name, (str, unicode))
582 if 0 <= name.find(':'):
583 (prefix, local_name) = name.split(':', 1)
584 assert self.inScopeNamespaces() is not None
585 namespace = self.inScopeNamespaces().get(prefix)
586 if namespace is None:
587 raise pyxb.SchemaValidationError('No namespace declared for QName %s prefix' % (name,))
588 else:
589 local_name = name
590
591 if self.defaultNamespace() is not None:
592 namespace = self.defaultNamespace()
593
594
595 if (namespace is None) and self.__fallbackToTargetNamespace:
596 namespace = self.targetNamespace()
597 if namespace is None:
598 raise pyxb.SchemaValidationError('QName %s with absent default namespace cannot be resolved' % (local_name,))
599
600
601
602 if (namespace != self.targetNamespace()):
603 namespace.validateComponentModel()
604 return pyxb.namespace.ExpandedName(namespace, local_name)
605
606 - def queueForResolution (self, component, depends_on=None):
607 """Forwards to L{queueForResolution()<Namespace.queueForResolution>} in L{targetNamespace()}."""
608 assert isinstance(component, _Resolvable_mixin)
609 return self.targetNamespace().queueForResolution(component, depends_on)
610
611
612
613
614