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
68 """Short-hand to requeue an object if the class implements _namespaceContext().
69 """
70 if (why is not None) and self._TraceResolution:
71 print 'Resolution delayed for %s: %s\n\tDepends on: %s' % (self, why, depends_on)
72 self._namespaceContext().queueForResolution(self, depends_on)
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
91
92
93
94 __unresolvedDependents = None
95
105
112
117
121
126
128 """Return the set of namespaces which some schema imported while
129 processing with this namespace as target."""
130 return frozenset(self.__importedNamespaces)
131
136
146
148 """Invoked to note that a component may have references that will need
149 to be resolved.
150
151 Newly created named components are often unresolved, as are components
152 which, in the course of resolution, are found to depend on another
153 unresolved component.
154
155 @param resolvable: An instance of L{_Resolvable_mixin} that is later to
156 be resolved.
157
158 @keyword depends_on: C{None}, or an instance of L{_Resolvable_mixin}
159 which C{resolvable} requires to be resolved in order to resolve
160 itself.
161
162 @return: C{resolvable}
163 """
164 assert isinstance(resolvable, _Resolvable_mixin)
165 if not resolvable.isResolved():
166 assert depends_on is None or isinstance(depends_on, _Resolvable_mixin)
167 self.__unresolvedComponents.append(resolvable)
168 if depends_on is not None and not depends_on.isResolved():
169 import pyxb.xmlschema.structures
170 assert isinstance(depends_on, _Resolvable_mixin)
171 assert isinstance(depends_on, pyxb.xmlschema.structures._NamedComponent_mixin)
172 self.__unresolvedDependents.setdefault(resolvable, set()).add(depends_on)
173 return resolvable
174
176 """Return C{True} iff this namespace has not been resolved."""
177 return self.__unresolvedComponents is not None
178
198
200 """Loop until all references within the associated resolvable objects
201 have been resolved.
202
203 This method iterates through all components on the unresolved list,
204 invoking the _resolve method of each. If the component could not be
205 resolved in this pass, it iis placed back on the list for the next
206 iteration. If an iteration completes without resolving any of the
207 unresolved components, a pyxb.NotInNamespaceError exception is raised.
208
209 @note: Do not invoke this until all top-level definitions for the
210 namespace have been provided. The resolution routines are entitled to
211 raise a validation exception if a reference to an unrecognized
212 component is encountered.
213 """
214 num_loops = 0
215 if not self.needsResolution():
216 return True
217
218 while 0 < len(self.__unresolvedComponents):
219
220
221
222 unresolved = self.__unresolvedComponents
223
224 num_loops += 1
225
226
227 self.__unresolvedComponents = []
228 self.__unresolvedDependents = {}
229 for resolvable in unresolved:
230
231 resolvable._resolve()
232
233
234 assert resolvable.isResolved() or (resolvable in self.__unresolvedComponents), 'Lost resolvable %s' % (resolvable,)
235
236
237
238
239
240 if (resolvable.isResolved() and (resolvable._clones() is not None)):
241 assert False
242 if self.__unresolvedComponents == unresolved:
243 if allow_unresolved:
244 return False
245
246
247
248 failed_components = []
249 import pyxb.xmlschema.structures
250 for d in self.__unresolvedComponents:
251 if isinstance(d, pyxb.xmlschema.structures._NamedComponent_mixin):
252 failed_components.append('%s named %s' % (d.__class__.__name__, d.name()))
253 else:
254 if isinstance(d, pyxb.xmlschema.structures.AttributeUse):
255 print d.attributeDeclaration()
256 failed_components.append('Anonymous %s' % (d.__class__.__name__,))
257 raise pyxb.NotInNamespaceError('Infinite loop in resolution:\n %s' % ("\n ".join(failed_components),))
258
259
260
261 self.__unresolvedComponents = None
262 self.__unresolvedDependents = None
263
264
265
266
267
268
269 self._releaseNamespaceContexts()
270
271 return True
272
276
278 """Returns a map from unresolved components to sets of components that
279 must be resolved first."""
280 return self.__unresolvedDependents
281
283 """Resolve all components in the sibling_namespaces.
284
285 @param sibling_namespaces : A set of namespaces expected to be closed
286 under dependency."""
287
288 for ns in sibling_namespaces:
289 ns.configureCategories([archive.NamespaceArchive._AnonymousCategory()])
290 ns.validateComponentModel()
291
292 def cmp_for_deps (ns1, ns2):
293 """Sort namespaces so dependencies get resolved first"""
294 if ns2 not in dependency_map.get(ns1, set()):
295 return -1
296 if ns1 not in dependency_map.get(ns2, set()):
297 return 1
298 return 0
299
300 need_resolved_set = set(sibling_namespaces)
301 dependency_map = {}
302 last_state = None
303 while need_resolved_set:
304 need_resolved_list = list(need_resolved_set)
305 if dependency_map:
306 need_resolved_list.sort(cmp_for_deps)
307 need_resolved_set = set()
308 dependency_map = {}
309 for ns in need_resolved_list:
310 if not ns.needsResolution():
311 continue
312
313 if not ns.resolveDefinitions(allow_unresolved=True):
314 print 'Holding incomplete resolution %s' % (ns.uri(),)
315 deps = dependency_map.setdefault(ns, set())
316 for (c, dcs) in ns._unresolvedDependents().iteritems():
317 for dc in dcs:
318 dns = dc.expandedName().namespace()
319 if dns != ns:
320 deps.add(dns)
321 print '%s depends on %s' % (ns, ' ; '.join([ str(_dns) for _dns in deps ]))
322 need_resolved_set.add(ns)
323
324
325
326
327
328 state = []
329 for ns in need_resolved_set:
330 state.append( (ns, len(ns._unresolvedComponents())) )
331 state = tuple(state)
332 if last_state == state:
333 raise pyxb.LogicError('Unexpected external dependency in sibling namespaces: %s' % ("\n ".join( [str(_ns) for _ns in need_resolved_set ]),))
334 last_state = state
335
336 -class NamespaceContext (object):
337 """Records information associated with namespaces at a DOM node.
338 """
339
340 - def __str__ (self):
341 rv = [ 'NamespaceContext ' ]
342 if self.defaultNamespace() is not None:
343 rv.extend([ '(defaultNamespace=', str(self.defaultNamespace()), ') '])
344 if self.targetNamespace() is not None:
345 rv.extend([ '(targetNamespace=', str(self.targetNamespace()), ') '])
346 rv.append("\n")
347 for (pfx, ns) in self.inScopeNamespaces().items():
348 if pfx is not None:
349 rv.append(' xmlns:%s=%s' % (pfx, str(ns)))
350 return ''.join(rv)
351
352 __TargetNamespaceAttributes = { }
353 @classmethod
354 - def _AddTargetNamespaceAttribute (cls, expanded_name, attribute_name):
357 @classmethod
358 - def _TargetNamespaceAttribute (cls, expanded_name):
360
361
362
363 __pendingReferencedNamespaces = None
364
365 - def defaultNamespace (self):
366 """The default namespace in effect at this node. E.g., C{xmlns="URN:default"}."""
367 return self.__defaultNamespace
368 __defaultNamespace = None
369
370
371
372
373
374
375
376 __fallbackToTargetNamespace = False
377
378 - def targetNamespace (self):
379 """The target namespace in effect at this node. Usually from the
380 C{targetNamespace} attribute. If no namespace is specified for the
381 schema, an absent namespace was assigned upon creation and will be
382 returned."""
383 return self.__targetNamespace
384 __targetNamespace = None
385
387 """Map from prefix strings to L{Namespace} instances associated with those
388 prefixes. The prefix C{None} identifies the default namespace."""
389 return self.__inScopeNamespaces
390 __inScopeNamespaces = None
391
392 - def prefixForNamespace (self, namespace):
393 """Return a prefix associated with the given namespace in this
394 context, or None if the namespace is the default or is not in
395 scope."""
396 for (pfx, ns) in self.__inScopeNamespaces.items():
397 if namespace == ns:
398 return pfx
399 return None
400
401 @classmethod
402 - def GetNodeContext (cls, node, **kw):
403 """Get the L{NamespaceContext} instance that was assigned to the node.
404
405 If none has been assigned and keyword parameters are present, create
406 one treating this as the root node and the keyword parameters as
407 configuration information (e.g., default_namespace).
408
409 @raise pyxb.LogicError: no context is available and the keywords
410 required to create one were not provided
411 """
412 try:
413 return node.__namespaceContext
414 except AttributeError:
415 return NamespaceContext(node, **kw)
416
417 - def setNodeContext (self, node):
419
420 - def processXMLNS (self, prefix, uri):
421 if not self.__mutableInScopeNamespaces:
422 self.__inScopeNamespaces = self.__inScopeNamespaces.copy()
423 self.__mutableInScopeNamespaces = True
424 if uri:
425 if prefix is None:
426 ns = self.__defaultNamespace = utility.NamespaceForURI(uri, create_if_missing=True)
427 self.__inScopeNamespaces[None] = self.__defaultNamespace
428 else:
429 ns = utility.NamespaceForURI(uri, create_if_missing=True)
430 self.__inScopeNamespaces[prefix] = ns
431
432
433
434
435
436 if self.__targetNamespace:
437 self.__targetNamespace._referenceNamespace(ns)
438 else:
439 self.__pendingReferencedNamespaces.add(ns)
440 else:
441
442
443
444
445
446 if prefix is not None:
447 raise pyxb.NamespaceError(self, 'Attempt to undefine non-default namespace %s' % (attr.localName,))
448 self.__inScopeNamespaces.pop(prefix, None)
449 self.__defaultNamespace = None
450
451 - def finalizeTargetNamespace (self, tns_uri=None, including_context=None):
452 if tns_uri is not None:
453 assert 0 < len(tns_uri)
454
455
456
457
458
459 self.__targetNamespace = utility.NamespaceForURI(tns_uri, create_if_missing=True)
460 elif self.__targetNamespace is None:
461 if including_context is not None:
462 self.__targetNamespace = including_context.targetNamespace()
463 self.__fallbackToTargetNamespace = True
464 elif tns_uri is None:
465 self.__targetNamespace = utility.CreateAbsentNamespace()
466 else:
467 self.__targetNamespace = utility.NamespaceForURI(tns_uri, create_if_missing=True)
468 if self.__pendingReferencedNamespaces is not None:
469 [ self.__targetNamespace._referenceNamespace(_ns) for _ns in self.__pendingReferencedNamespaces ]
470 self.__pendingReferencedNamespace = None
471 assert self.__targetNamespace is not None
472 if (not self.__fallbackToTargetNamespace) and self.__targetNamespace.isAbsentNamespace():
473 self.__fallbackToTargetNamespace = True
474
475 - def __init__ (self,
476 dom_node=None,
477 parent_context=None,
478 including_context=None,
479 recurse=True,
480 default_namespace=None,
481 target_namespace=None,
482 in_scope_namespaces=None,
483 expanded_name=None,
484 finalize_target_namespace=True):
485 """Determine the namespace context that should be associated with the
486 given node and, optionally, its element children.
487
488 @param dom_node: The DOM node
489 @type dom_node: C{xml.dom.Element}
490 @keyword parent_context: Optional value that specifies the context
491 associated with C{dom_node}'s parent node. If not provided, only the
492 C{xml} namespace is in scope.
493 @type parent_context: L{NamespaceContext}
494 @keyword recurse: If True (default), create namespace contexts for all
495 element children of C{dom_node}
496 @type recurse: C{bool}
497 @keyword default_namespace: Optional value to set as the default
498 namespace. Values from C{parent_context} would override this, as
499 would an C{xmlns} attribute in the C{dom_node}.
500 @type default_namespace: L{NamespaceContext}
501 @keyword target_namespace: Optional value to set as the target
502 namespace. Values from C{parent_context} would override this, as
503 would a C{targetNamespace} attribute in the C{dom_node}
504 @type target_namespace: L{NamespaceContext}
505 @keyword in_scope_namespaces: Optional value to set as the initial set
506 of in-scope namespaces. The always-present namespaces are added to
507 this if necessary.
508 @type in_scope_namespaces: C{dict} mapping C{string} to L{Namespace}.
509 """
510 import builtin
511
512 if dom_node is not None:
513 try:
514 assert dom_node.__namespaceContext is None
515 except AttributeError:
516 pass
517 dom_node.__namespaceContext = self
518
519 self.__defaultNamespace = default_namespace
520 self.__targetNamespace = target_namespace
521 self.__inScopeNamespaces = builtin._UndeclaredNamespaceMap
522 self.__mutableInScopeNamespaces = False
523
524 if in_scope_namespaces is not None:
525 if parent_context is not None:
526 raise LogicError('Cannot provide both parent_context and in_scope_namespaces')
527 self.__inScopeNamespaces = builtin._UndeclaredNamespaceMap.copy()
528 self.__inScopeNamespaces.update(in_scope_namespaces)
529 self.__mutableInScopeNamespaces = True
530
531 if parent_context is not None:
532 self.__inScopeNamespaces = parent_context.inScopeNamespaces()
533 self.__mutableInScopeNamespaces = False
534 self.__defaultNamespace = parent_context.defaultNamespace()
535 self.__targetNamespace = parent_context.targetNamespace()
536 self.__fallbackToTargetNamespace = parent_context.__fallbackToTargetNamespace
537
538 if self.__targetNamespace is None:
539 self.__pendingReferencedNamespaces = set()
540 attribute_map = {}
541 if dom_node is not None:
542 if expanded_name is None:
543 expanded_name = pyxb.namespace.ExpandedName(dom_node)
544 for ai in range(dom_node.attributes.length):
545 attr = dom_node.attributes.item(ai)
546 if builtin.XMLNamespaces.uri() == attr.namespaceURI:
547 prefix = attr.localName
548 if 'xmlns' == prefix:
549 prefix = None
550 self.processXMLNS(prefix, attr.value)
551 else:
552 if attr.namespaceURI is not None:
553 uri = utility.NamespaceForURI(attr.namespaceURI, create_if_missing=True)
554 key = pyxb.namespace.ExpandedName(uri, attr.localName)
555 else:
556 key = pyxb.namespace.ExpandedName(None, attr.localName)
557 attribute_map[key] = attr.value
558
559 if finalize_target_namespace:
560 tns_uri = None
561 tns_attr = self._TargetNamespaceAttribute(expanded_name)
562 if tns_attr is not None:
563 tns_uri = attribute_map.get(tns_attr)
564 self.finalizeTargetNamespace(tns_uri, including_context=including_context)
565
566
567
568
569 if (dom_node is not None) and recurse:
570 from xml.dom import Node
571 assert Node.ELEMENT_NODE == dom_node.nodeType
572 for cn in dom_node.childNodes:
573 if Node.ELEMENT_NODE == cn.nodeType:
574 NamespaceContext(cn, self, True)
575
576 - def interpretQName (self, name, namespace=None):
577 """Convert the provided name into an L{ExpandedName}, i.e. a tuple of
578 L{Namespace} and local name.
579
580 If the name includes a prefix, that prefix must map to an in-scope
581 namespace in this context. Absence of a prefix maps to
582 L{defaultNamespace()}, which must be provided (or defaults to the
583 target namespace, if that is absent).
584
585 @param name: A QName.
586 @type name: C{str} or C{unicode}
587 @param name: Optional namespace to use for unqualified names when
588 there is no default namespace. Note that a defined default namespace,
589 even if absent, supersedes this value.
590 @return: An L{ExpandedName} tuple: ( L{Namespace}, C{str} )
591 @raise pyxb.SchemaValidationError: The prefix is not in scope
592 @raise pyxb.SchemaValidationError: No prefix is given and the default namespace is absent
593 """
594 assert isinstance(name, (str, unicode))
595 if 0 <= name.find(':'):
596 (prefix, local_name) = name.split(':', 1)
597 assert self.inScopeNamespaces() is not None
598 namespace = self.inScopeNamespaces().get(prefix)
599 if namespace is None:
600 raise pyxb.SchemaValidationError('No namespace declared for QName %s prefix' % (name,))
601 else:
602 local_name = name
603
604 if self.defaultNamespace() is not None:
605 namespace = self.defaultNamespace()
606
607
608 if (namespace is None) and self.__fallbackToTargetNamespace:
609 namespace = self.targetNamespace()
610 if namespace is None:
611 raise pyxb.SchemaValidationError('QName %s with absent default namespace cannot be resolved' % (local_name,))
612
613
614
615 if (namespace != self.targetNamespace()):
616 namespace.validateComponentModel()
617 return pyxb.namespace.ExpandedName(namespace, local_name)
618
619 - def queueForResolution (self, component, depends_on=None):
620 """Forwards to L{queueForResolution()<Namespace.queueForResolution>} in L{targetNamespace()}."""
621 assert isinstance(component, _Resolvable_mixin)
622 return self.targetNamespace().queueForResolution(component, depends_on)
623
624
625
626
627