Package pyxb :: Package namespace :: Module resolution
[hide private]
[frames] | no frames]

Source Code for Module pyxb.namespace.resolution

  1  # Copyright 2009, Peter A. Bigot 
  2  # 
  3  # Licensed under the Apache License, Version 2.0 (the "License"); you may 
  4  # not use this file except in compliance with the License. You may obtain a 
  5  # copy of the License at: 
  6  # 
  7  #            http://www.apache.org/licenses/LICENSE-2.0 
  8  # 
  9  # Unless required by applicable law or agreed to in writing, software 
 10  # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 
 11  # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 
 12  # License for the specific language governing permissions and limitations 
 13  # under the License. 
 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 
24 25 -class _Resolvable_mixin (pyxb.cscRoot):
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 #_TraceResolution = True 35 _TraceResolution = False 36
37 - def isResolved (self):
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
43 - def _resolve (self):
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
67 - def _queueForResolution (self, why=None):
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' % (self, why) 72 self._namespaceContext().queueForResolution(self)
73
74 -class _NamespaceResolution_mixin (pyxb.cscRoot):
75 """Mix-in that aggregates those aspects of XMLNamespaces relevant to 76 resolving component references. 77 """ 78 79 # A set of namespaces which some schema imported while processing with 80 # this namespace as target. 81 __importedNamespaces = None 82 83 # A set of namespaces which appear in namespace declarations of schema 84 # with this namespace as target. 85 __referencedNamespaces = None 86 87 # A list of Namespace._Resolvable_mixin instances that have yet to be 88 # resolved. 89 __unresolvedComponents = None 90
91 - def _reset (self):
92 """CSC extension to reset fields of a Namespace. 93 94 This one handles component-resolution--related data.""" 95 getattr(super(_NamespaceResolution_mixin, self), '_reset', lambda *args, **kw: None)() 96 self.__unresolvedComponents = [] 97 self.__importedNamespaces = set() 98 self.__referencedNamespaces = set()
99
100 - def _getState_csc (self, kw):
101 kw.update({ 102 'importedNamespaces': self.__importedNamespaces, 103 'referencedNamespaces': self.__referencedNamespaces, 104 }) 105 return getattr(super(_NamespaceResolution_mixin, self), '_getState_csc', lambda _kw: _kw)(kw)
106
107 - def _setState_csc (self, kw):
108 self.__importedNamespaces = kw['importedNamespaces'] 109 self.__referencedNamespaces = kw['referencedNamespaces'] 110 return getattr(super(_NamespaceResolution_mixin, self), '_setState_csc', lambda _kw: self)(kw)
111
112 - def importNamespace (self, namespace):
113 self.__importedNamespaces.add(namespace) 114 return self
115
116 - def _referenceNamespace (self, namespace):
117 self._activate() 118 self.__referencedNamespaces.add(namespace) 119 return self
120
121 - def importedNamespaces (self):
122 """Return the set of namespaces which some schema imported while 123 processing with this namespace as target.""" 124 return frozenset(self.__importedNamespaces)
125
126 - def _transferReferencedNamespaces (self, module_record):
127 assert isinstance(module_record, archive.ModuleRecord) 128 module_record._setReferencedNamespaces(self.__referencedNamespaces) 129 self.__referencedNamespaces.clear()
130
131 - def referencedNamespaces (self):
132 """Return the set of namespaces which appear in namespace declarations 133 of schema with this namespace as target.""" 134 return frozenset(self.__referencedNamespaces) 135 rn = self.__referencedNamespaces.copy() 136 for mr in self.moduleRecords(): 137 if mr.isIncorporated(): 138 rn.update(mr.referencedNamespaces()) 139 return rn
140
141 - def queueForResolution (self, resolvable):
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
157 - def needsResolution (self):
158 """Return C{True} iff this namespace has not been resolved.""" 159 return self.__unresolvedComponents is not None
160
161 - def _replaceComponent_csc (self, existing_def, replacement_def):
162 """Replace a component definition if present in the list of unresolved components. 163 """ 164 try: 165 index = self.__unresolvedComponents.index(existing_def) 166 print 'Replacing unresolved %s' % (existing_def,) 167 if (replacement_def is None) or (replacement_def in self.__unresolvedComponents): 168 del self.__unresolvedComponents[index] 169 else: 170 assert isinstance(replacement_def, _Resolvable_mixin) 171 self.__unresolvedComponents[index] = replacement_def 172 except ValueError: 173 pass 174 return getattr(super(_NamespaceResolution_mixin, self), '_replaceComponent_csc', lambda *args, **kw: replacement_def)(existing_def, replacement_def)
175
176 - def resolveDefinitions (self, allow_unresolved=False):
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 # Save the list of unresolved objects, reset the list to capture 197 # any new objects defined during resolution, and attempt the 198 # resolution for everything that isn't resolved. 199 unresolved = self.__unresolvedComponents 200 #print 'Looping for %d unresolved definitions: %s' % (len(unresolved), ' '.join([ str(_r) for _r in unresolved])) 201 num_loops += 1 202 #assert num_loops < 18 203 204 self.__unresolvedComponents = [] 205 for resolvable in unresolved: 206 # Attempt the resolution. 207 resolvable._resolve() 208 209 # Either we resolved it, or we queued it to try again later 210 assert resolvable.isResolved() or (resolvable in self.__unresolvedComponents), 'Lost resolvable %s' % (resolvable,) 211 212 # We only clone things that have scope None. We never 213 # resolve things that have scope None. Therefore, we 214 # should never have resolved something that has 215 # clones. 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 # This only happens if we didn't code things right, or the 222 # there is a circular dependency in some named component 223 # (i.e., the schema designer didn't do things right). 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 # Replace the list of unresolved components with None, so that 236 # attempts to subsequently add another component fail. 237 self.__unresolvedComponents = None 238 239 # NOTE: Dependencies may require that we keep these around for a while 240 # longer. 241 # 242 # Remove the namespace context from everything, since we won't be 243 # resolving anything else. 244 self._releaseNamespaceContexts() 245 246 return True
247
248 - def _unresolvedComponents (self):
249 """Returns a reference to the list of unresolved components.""" 250 return self.__unresolvedComponents
251
252 -def ResolveSiblingNamespaces (sibling_namespaces, origin_uid):
253 for ns in sibling_namespaces: 254 ns.configureCategories([archive.NamespaceArchive._AnonymousCategory()]) 255 ns.validateComponentModel() 256 257 need_resolved = set(sibling_namespaces) 258 while need_resolved: 259 new_nr = set() 260 for ns in need_resolved: 261 if not ns.needsResolution(): 262 continue 263 #print 'Attempting resolution %s' % (ns.uri(),) 264 if not ns.resolveDefinitions(allow_unresolved=True): 265 print 'Holding incomplete resolution %s' % (ns.uri(),) 266 new_nr.add(ns) 267 if need_resolved == new_nr: 268 raise pyxb.LogicError('Unexpected external dependency in sibling namespaces: %s' % ("\n ".join( [str(_ns) for _ns in need_resolved ]),)) 269 need_resolved = new_nr
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):
290 assert expanded_name is not None 291 cls.__TargetNamespaceAttributes[expanded_name] = attribute_name
292 @classmethod
293 - def _TargetNamespaceAttribute (cls, expanded_name):
294 return cls.__TargetNamespaceAttributes.get(expanded_name, None)
295 296 # Support for holding onto referenced namespaces until we have a target 297 # namespace to give them to. 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 # If C{True}, this context is within a schema that has no target 306 # namespace, and we should use the target namespace as a fallback if no 307 # default namespace is available and no namespace prefix appears on a 308 # QName. This situation arises when a top-level schema has an absent 309 # target namespace, or when a schema with an absent target namespace is 310 # being included into a schema with a non-absent target namespace. 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
321 - def inScopeNamespaces (self):
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):
353 node.__namespaceContext = self
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 #if ns.prefix() is None: 367 # ns.setPrefix(prefix) 368 # @todo should we record prefix in namespace so we can use it 369 # during generation? I'd rather make the user specify what to 370 # use. 371 if self.__targetNamespace: 372 self.__targetNamespace._referenceNamespace(ns) 373 else: 374 self.__pendingReferencedNamespaces.add(ns) 375 else: 376 # NB: XMLNS 6.2 says that you can undefine a default 377 # namespace, but does not say anything explicitly about 378 # undefining a prefixed namespace. XML-Infoset 2.2 379 # paragraph 6 implies you can do this, but expat blows up 380 # if you try it. I don't think it's legal. 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 # Do not prevent overwriting target namespace; need this for WSDL 390 # files where an embedded schema inadvertently inherits a target 391 # namespace from its enclosing definitions element. Note that if 392 # we don't check this here, we do have to check it when schema 393 # documents are included into parent schema documents. 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): # MUST BE True for WSDL to work with minidom
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 # Store in each node the in-scope namespaces at that node; 502 # we'll need them for QName interpretation of attribute 503 # values. 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 # Context default supersedes caller-provided namespace 539 if self.defaultNamespace() is not None: 540 namespace = self.defaultNamespace() 541 # If there's no default namespace, but there is a fallback 542 # namespace, use that instead. 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 # Anything we're going to look stuff up in requires a component model. 548 # Make sure we can load one, unless we're looking up in the thing 549 # we're constructing (in which case it's being built right now). 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 ## Local Variables: 560 ## fill-column:78 561 ## End: 562