1# -*- coding: utf-8 -*-
2# Copyright 2009-2013, Peter A. Bigot
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain a
6# copy of the License at:
7#
8#            http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
16"""Classes and global objects related to resolving U{XML
17Namespaces<http://www.w3.org/TR/2006/REC-xml-names-20060816/index.html>}."""
18
19import logging
20import pyxb
21import pyxb.utils.utility
22from pyxb.namespace import archive, utility
23from pyxb.utils import six
24
25_log = logging.getLogger(__name__)
26
27class _Resolvable_mixin (pyxb.cscRoot):
28    """Mix-in indicating that this object may have references to unseen named components.
29
30    This class is mixed-in to those XMLSchema components that have a reference
31    to another component that is identified by a QName.  Resolution of that
32    component may need to be delayed if the definition of the component has
33    not yet been read.
34    """
35
36    #_TraceResolution = True
37    _TraceResolution = False
38
39    def isResolved (self):
40        """Determine whether this named component is resolved.
41
42        Override this in the child class."""
43        raise NotImplementedError("_Resolvable_mixin.isResolved in %s"% (type(self).__name__,))
44
45    def _resolve (self):
46        """Perform whatever steps are required to resolve this component.
47
48        Resolution is performed in the context of the namespace to which the
49        component belongs.  Invoking this method may fail to complete the
50        resolution process if the component itself depends on unresolved
51        components.  The sole caller of this should be
52        L{_NamespaceResolution_mixin.resolveDefinitions}.
53
54        This method is permitted (nay, encouraged) to raise an exception if
55        resolution requires interpreting a QName and the named component
56        cannot be found.
57
58        Override this in the child class.  In the prefix, if L{isResolved} is
59        true, return right away.  If something prevents you from completing
60        resolution, invoke L{self._queueForResolution()} (so it is retried
61        later) and immediately return self.  Prior to leaving after successful
62        resolution discard any cached dom node by setting C{self.__domNode=None}.
63
64        @return: C{self}, whether or not resolution succeeds.
65        @raise pyxb.SchemaValidationError: if resolution requlres a reference to an unknown component
66        """
67        raise NotImplementedError("_Resolvable_mixin._resolve in %s"% (type(self).__name__,))
68
69    def _queueForResolution (self, why=None, depends_on=None):
70        """Short-hand to requeue an object if the class implements _namespaceContext().
71        """
72        if (why is not None) and self._TraceResolution:
73            _log.info('Resolution delayed for %s: %s\n\tDepends on: %s', self, why, depends_on)
74        self._namespaceContext().queueForResolution(self, depends_on)
75
76class _NamespaceResolution_mixin (pyxb.cscRoot):
77    """Mix-in that aggregates those aspects of XMLNamespaces relevant to
78    resolving component references.
79    """
80
81    # A set of namespaces which some schema imported while processing with
82    # this namespace as target.
83    __importedNamespaces = None
84
85    # A set of namespaces which appear in namespace declarations of schema
86    # with this namespace as target.
87    __referencedNamespaces = None
88
89    # A list of Namespace._Resolvable_mixin instances that have yet to be
90    # resolved.
91    __unresolvedComponents = None
92
93    # A map from Namespace._Resolvable_mixin instances in
94    # __unresolvedComponents to sets of other unresolved objects on which they
95    # depend.
96    __unresolvedDependents = None
97
98    def _reset (self):
99        """CSC extension to reset fields of a Namespace.
100
101        This one handles component-resolution--related data."""
102        getattr(super(_NamespaceResolution_mixin, self), '_reset', lambda *args, **kw: None)()
103        self.__unresolvedComponents = []
104        self.__unresolvedDependents = {}
105        self.__importedNamespaces = set()
106        self.__referencedNamespaces = set()
107
108    def _getState_csc (self, kw):
109        kw.update({
110                'importedNamespaces': self.__importedNamespaces,
111                'referencedNamespaces': self.__referencedNamespaces,
112                })
113        return getattr(super(_NamespaceResolution_mixin, self), '_getState_csc', lambda _kw: _kw)(kw)
114
115    def _setState_csc (self, kw):
116        self.__importedNamespaces = kw['importedNamespaces']
117        self.__referencedNamespaces = kw['referencedNamespaces']
118        return getattr(super(_NamespaceResolution_mixin, self), '_setState_csc', lambda _kw: self)(kw)
119
120    def importNamespace (self, namespace):
121        self.__importedNamespaces.add(namespace)
122        return self
123
124    def _referenceNamespace (self, namespace):
125        self._activate()
126        self.__referencedNamespaces.add(namespace)
127        return self
128
129    def importedNamespaces (self):
130        """Return the set of namespaces which some schema imported while
131        processing with this namespace as target."""
132        return frozenset(self.__importedNamespaces)
133
134    def _transferReferencedNamespaces (self, module_record):
135        assert isinstance(module_record, archive.ModuleRecord)
136        module_record._setReferencedNamespaces(self.__referencedNamespaces)
137        self.__referencedNamespaces.clear()
138
139    def referencedNamespaces (self):
140        """Return the set of namespaces which appear in namespace declarations
141        of schema with this namespace as target."""
142        return frozenset(self.__referencedNamespaces)
143
144    def queueForResolution (self, resolvable, depends_on=None):
145        """Invoked to note that a component may have references that will need
146        to be resolved.
147
148        Newly created named components are often unresolved, as are components
149        which, in the course of resolution, are found to depend on another
150        unresolved component.
151
152        @param resolvable: An instance of L{_Resolvable_mixin} that is later to
153        be resolved.
154
155        @keyword depends_on: C{None}, or an instance of L{_Resolvable_mixin}
156        which C{resolvable} requires to be resolved in order to resolve
157        itself.
158
159        @return: C{resolvable}
160        """
161        assert isinstance(resolvable, _Resolvable_mixin)
162        if not resolvable.isResolved():
163            assert depends_on is None or isinstance(depends_on, _Resolvable_mixin)
164            self.__unresolvedComponents.append(resolvable)
165            if depends_on is not None and not depends_on.isResolved():
166                from pyxb.xmlschema import structures
167                assert isinstance(depends_on, _Resolvable_mixin)
168                assert isinstance(depends_on, structures._NamedComponent_mixin)
169                self.__unresolvedDependents.setdefault(resolvable, set()).add(depends_on)
170        return resolvable
171
172    def needsResolution (self):
173        """Return C{True} iff this namespace has not been resolved."""
174        return self.__unresolvedComponents is not None
175
176    def _replaceComponent_csc (self, existing_def, replacement_def):
177        """Replace a component definition if present in the list of unresolved components.
178        """
179        try:
180            index = self.__unresolvedComponents.index(existing_def)
181            if (replacement_def is None) or (replacement_def in self.__unresolvedComponents):
182                del self.__unresolvedComponents[index]
183            else:
184                assert isinstance(replacement_def, _Resolvable_mixin)
185                self.__unresolvedComponents[index] = replacement_def
186            # Rather than assume the replacement depends on the same
187            # resolvables as the original, just wipe the dependency record:
188            # it'll get recomputed later if it's still important.
189            if existing_def in self.__unresolvedDependents:
190                del self.__unresolvedDependents[existing_def]
191        except ValueError:
192            pass
193        return getattr(super(_NamespaceResolution_mixin, self), '_replaceComponent_csc', lambda *args, **kw: replacement_def)(existing_def, replacement_def)
194
195    def resolveDefinitions (self, allow_unresolved=False):
196        """Loop until all references within the associated resolvable objects
197        have been resolved.
198
199        This method iterates through all components on the unresolved list,
200        invoking the _resolve method of each.  If the component could not be
201        resolved in this pass, it iis placed back on the list for the next
202        iteration.  If an iteration completes without resolving any of the
203        unresolved components, a pyxb.NotInNamespaceError exception is raised.
204
205        @note: Do not invoke this until all top-level definitions for the
206        namespace have been provided.  The resolution routines are entitled to
207        raise a validation exception if a reference to an unrecognized
208        component is encountered.
209        """
210        if not self.needsResolution():
211            return True
212
213        while 0 < len(self.__unresolvedComponents):
214            # Save the list of unresolved objects, reset the list to capture
215            # any new objects defined during resolution, and attempt the
216            # resolution for everything that isn't resolved.
217            unresolved = self.__unresolvedComponents
218
219            self.__unresolvedComponents = []
220            self.__unresolvedDependents = {}
221            for resolvable in unresolved:
222                # Attempt the resolution.
223                resolvable._resolve()
224
225                # Either we resolved it, or we queued it to try again later
226                assert resolvable.isResolved() or (resolvable in self.__unresolvedComponents), 'Lost resolvable %s' % (resolvable,)
227
228                # We only clone things that have scope None.  We never
229                # resolve things that have scope None.  Therefore, we
230                # should never have resolved something that has
231                # clones.
232                if (resolvable.isResolved() and (resolvable._clones() is not None)):
233                    assert False
234            if self.__unresolvedComponents == unresolved:
235                if allow_unresolved:
236                    return False
237                # This only happens if we didn't code things right, or the
238                # there is a circular dependency in some named component
239                # (i.e., the schema designer didn't do things right).
240                failed_components = []
241                from pyxb.xmlschema import structures
242                for d in self.__unresolvedComponents:
243                    if isinstance(d, structures._NamedComponent_mixin):
244                        failed_components.append('%s named %s' % (d.__class__.__name__, d.name()))
245                    else:
246                        failed_components.append('Anonymous %s' % (d.__class__.__name__,))
247                raise pyxb.NotInNamespaceError('Infinite loop in resolution:\n  %s' % ("\n  ".join(failed_components),))
248
249        # Replace the list of unresolved components with None, so that
250        # attempts to subsequently add another component fail.
251        self.__unresolvedComponents = None
252        self.__unresolvedDependents = None
253
254        # NOTE: Dependencies may require that we keep these around for a while
255        # longer.
256        #
257        # Remove the namespace context from everything, since we won't be
258        # resolving anything else.
259        self._releaseNamespaceContexts()
260
261        return True
262
263    def _unresolvedComponents (self):
264        """Returns a reference to the list of unresolved components."""
265        return self.__unresolvedComponents
266
267    def _unresolvedDependents (self):
268        """Returns a map from unresolved components to sets of components that
269        must be resolved first."""
270        return self.__unresolvedDependents
271
272def ResolveSiblingNamespaces (sibling_namespaces):
273    """Resolve all components in the sibling_namespaces.
274
275    @param sibling_namespaces : A set of namespaces expected to be closed
276    under dependency."""
277
278    for ns in sibling_namespaces:
279        ns.configureCategories([archive.NamespaceArchive._AnonymousCategory()])
280        ns.validateComponentModel()
281
282    def __keyForCompare (dependency_map):
283        """Sort namespaces so dependencies get resolved first.
284
285        Uses the trick underlying functools.cmp_to_key(), but optimized for
286        this special case.  The dependency map is incorporated into the class
287        definition by scope.
288        """
289        class K (object):
290            def __init__ (self, ns, *args):
291                self.__ns = ns
292
293            # self compares less than other if self.ns is in the dependency set
294            # of other.ns but not vice-versa.
295            def __lt__ (self, other):
296                return ((self.__ns in dependency_map.get(other.__ns, set())) \
297                            and not (other.__ns in dependency_map.get(self.__ns, set())))
298
299            # self compares equal to other if their namespaces are either
300            # mutually dependent or independent.
301            def __eq__ (self, other):
302                return (self.__ns in dependency_map.get(other.__ns, set())) == (other.__ns in dependency_map.get(self.__ns, set()))
303
304            # All other order metrics are derived.
305            def __ne__ (self, other):
306                return not self.__eq__(other)
307            def __le__ (self, other):
308                return self.__lt__(other) or self.__eq__(other)
309            def __gt__ (self, other):
310                return other.__lt__(self.__ns)
311            def __ge__ (self, other):
312                return other.__lt__(self.__ns) or self.__eq__(other)
313        return K
314
315    need_resolved_set = set(sibling_namespaces)
316    dependency_map = {}
317    last_state = None
318    while need_resolved_set:
319        need_resolved_list = list(need_resolved_set)
320        if dependency_map:
321            need_resolved_list.sort(key=__keyForCompare(dependency_map))
322        need_resolved_set = set()
323        dependency_map = {}
324        for ns in need_resolved_list:
325            if not ns.needsResolution():
326                continue
327            if not ns.resolveDefinitions(allow_unresolved=True):
328                deps = dependency_map.setdefault(ns, set())
329                for (c, dcs) in six.iteritems(ns._unresolvedDependents()):
330                    for dc in dcs:
331                        dns = dc.expandedName().namespace()
332                        if dns != ns:
333                            deps.add(dns)
334                _log.info('Holding incomplete resolution %s depending on: ', ns.uri(), six.u(' ; ').join([ six.text_type(_dns) for _dns in deps ]))
335                need_resolved_set.add(ns)
336        # Exception termination check: if we have the same set of incompletely
337        # resolved namespaces, and each has the same number of unresolved
338        # components, assume there's an truly unresolvable dependency: either
339        # due to circularity, or because there was an external namespace that
340        # was missed from the sibling list.
341        state = []
342        for ns in need_resolved_set:
343            state.append( (ns, len(ns._unresolvedComponents())) )
344        state = tuple(state)
345        if last_state == state:
346            raise pyxb.LogicError('Unexpected external dependency in sibling namespaces: %s' % (six.u('\n  ').join( [six.text_type(_ns) for _ns in need_resolved_set ]),))
347        last_state = state
348
349@six.python_2_unicode_compatible
350class NamespaceContext (object):
351    """Records information associated with namespaces at a DOM node.
352    """
353
354    def __str__ (self):
355        rv = [ six.u('NamespaceContext ') ]
356        if self.defaultNamespace() is not None:
357            rv.extend([ '(defaultNamespace=', six.text_type(self.defaultNamespace()), ') '])
358        if self.targetNamespace() is not None:
359            rv.extend([ '(targetNamespace=', six.text_type(self.targetNamespace()), ') '])
360        rv.append("\n")
361        for (pfx, ns) in six.iteritems(self.inScopeNamespaces()):
362            if pfx is not None:
363                rv.append('  xmlns:%s=%s' % (pfx, six.text_type(ns)))
364        return six.u('').join(rv)
365
366    __ContextStack = []
367    @classmethod
368    def PushContext (cls, ctx):
369        """Make C{ctx} the currently active namespace context.
370
371        Prior contexts are retained on a LIFO stack."""
372        assert isinstance(ctx, cls)
373        cls.__ContextStack.append(ctx)
374        return ctx
375
376    @classmethod
377    def Current (cls):
378        """Access the currently active namespace context.
379
380        If no context is active, C{None} is returned.  This probably
381        represents mis-use of the infrastructure (viz., failure to record the
382        context within which a QName must be resolved)."""
383        if cls.__ContextStack:
384            return cls.__ContextStack[-1]
385        return None
386
387    @classmethod
388    def PopContext (cls):
389        """Discard the currently active namespace context, restoring its
390        predecessor.
391
392        The discarded context is returned."""
393        return cls.__ContextStack.pop()
394
395    __TargetNamespaceAttributes = { }
396    @classmethod
397    def _AddTargetNamespaceAttribute (cls, expanded_name, attribute_name):
398        assert expanded_name is not None
399        cls.__TargetNamespaceAttributes[expanded_name] = attribute_name
400    @classmethod
401    def _TargetNamespaceAttribute (cls, expanded_name):
402        return cls.__TargetNamespaceAttributes.get(expanded_name)
403
404    # Support for holding onto referenced namespaces until we have a target
405    # namespace to give them to.
406    __pendingReferencedNamespaces = None
407
408    def defaultNamespace (self):
409        """The default namespace in effect at this node.  E.g., C{xmlns="URN:default"}."""
410        return self.__defaultNamespace
411    __defaultNamespace = None
412
413    def setDefaultNamespace (self, default_namespace):
414        """Set the default namespace for the generated document.
415
416        Even if invoked post construction, the default namespace will affect
417        the entire document, as all namespace declarations are placed in the
418        document root.
419
420        @param default_namespace: The namespace to be defined as the default
421        namespace in the top-level element of the document.  May be provided
422        as a real namespace, or just its URI.
423        @type default_namespace: L{pyxb.namespace.Namespace} or C{str} or
424        C{unicode}.
425        """
426
427        if isinstance(default_namespace, six.string_types):
428            default_namespace = utility.NamespaceForURI(default_namespace, create_if_missing=True)
429        if (default_namespace is not None) and default_namespace.isAbsentNamespace():
430            raise pyxb.UsageError('Default namespace must not be an absent namespace')
431        self.__defaultNamespace = default_namespace
432
433    # If C{True}, this context is within a schema that has no target
434    # namespace, and we should use the target namespace as a fallback if no
435    # default namespace is available and no namespace prefix appears on a
436    # QName.  This situation arises when a top-level schema has an absent
437    # target namespace, or when a schema with an absent target namespace is
438    # being included into a schema with a non-absent target namespace.
439    __fallbackToTargetNamespace = False
440
441    def targetNamespace (self):
442        """The target namespace in effect at this node.  Usually from the
443        C{targetNamespace} attribute.  If no namespace is specified for the
444        schema, an absent namespace was assigned upon creation and will be
445        returned."""
446        return self.__targetNamespace
447    __targetNamespace = None
448
449    def inScopeNamespaces (self):
450        """Map from prefix strings to L{Namespace} instances associated with those
451        prefixes.  The prefix C{None} identifies the default namespace."""
452        return self.__inScopeNamespaces
453    __inScopeNamespaces = None
454
455    """Map from L{Namespace} instances to sets of prefix strings associated
456    with the namespace.  The default namespace is not represented."""
457    __inScopePrefixes = None
458
459    def __removePrefixMap (self, pfx):
460        ns = self.__inScopeNamespaces.pop(pfx, None)
461        if ns is not None:
462            pfxs = self.__inScopePrefixes.get(ns)
463            if pfxs is not None:
464                pfxs.discard(pfx)
465
466    def __addPrefixMap (self, pfx, ns):
467        # Any previous assignment must have already been removed
468        self.__inScopeNamespaces[pfx] = ns
469        self.__inScopePrefixes.setdefault(ns, set()).add(pfx)
470
471    def __clonePrefixMap (self):
472        self.__inScopeNamespaces = self.__inScopeNamespaces.copy()
473        isp = {}
474        for (ns, pfxs) in six.iteritems(self.__inScopePrefixes):
475            isp[ns] = pfxs.copy()
476        self.__inScopePrefixes = isp
477
478    # Class-scope initial map from prefix to namespace
479    __InitialScopeNamespaces = None
480    # Class-scope initial map from namespace to prefix(es)
481    __InitialScopePrefixes = None
482    # Instance-specific initial map from prefix to namespace
483    __initialScopeNamespaces = None
484    # Instance-specific initial map from namespace to prefix(es)
485    __initialScopePrefixes = None
486
487
488    @classmethod
489    def __BuildInitialPrefixMap (cls):
490        if cls.__InitialScopeNamespaces is not None:
491            return
492        from pyxb.namespace import builtin
493        cls.__InitialScopeNamespaces = builtin._UndeclaredNamespaceMap
494        cls.__InitialScopePrefixes = {}
495        for (pfx, ns) in six.iteritems(cls.__InitialScopeNamespaces):
496            cls.__InitialScopePrefixes.setdefault(ns, set()).add(pfx)
497
498    def prefixForNamespace (self, namespace):
499        """Return a prefix associated with the given namespace in this
500        context, or None if the namespace is the default or is not in
501        scope."""
502        pfxs = self.__inScopePrefixes.get(namespace)
503        if pfxs:
504            return next(iter(pfxs))
505        return None
506
507    @classmethod
508    def GetNodeContext (cls, node, **kw):
509        """Get the L{NamespaceContext} instance that was assigned to the node.
510
511        If none has been assigned and keyword parameters are present, create
512        one treating this as the root node and the keyword parameters as
513        configuration information (e.g., default_namespace).
514
515        @raise pyxb.LogicError: no context is available and the keywords
516        required to create one were not provided
517        """
518        try:
519            return node.__namespaceContext
520        except AttributeError:
521            return NamespaceContext(node, **kw)
522
523    def setNodeContext (self, node):
524        node.__namespaceContext = self
525
526    # Integer counter to help generate unique namespace prefixes
527    __namespacePrefixCounter = None
528
529    def declareNamespace (self, namespace, prefix=None, add_to_map=False):
530        """Record the given namespace as one to be used in this document.
531
532        @param namespace: The namespace to be associated with the document.
533        @type namespace: L{pyxb.namespace.Namespace}
534
535        @keyword prefix: Optional prefix to be used with this namespace.  If
536        not provided, a unique prefix is generated or a standard prefix is
537        used, depending on the namespace.
538
539        @return: a prefix that may be used with the namespace.  If C{prefix}
540        was C{None} the return value may be a previously-assigned prefix.
541
542        @todo: ensure multiple namespaces do not share the same prefix
543        @todo: provide default prefix in L{pyxb.namespace.Namespace}
544        """
545        if not isinstance(namespace, pyxb.namespace.Namespace):
546            raise pyxb.UsageError('declareNamespace: must be given a namespace instance')
547        if namespace.isAbsentNamespace():
548            raise pyxb.UsageError('declareNamespace: namespace must not be an absent namespace')
549        if prefix is None:
550            prefix = namespace.prefix()
551        if prefix is None:
552            pfxs = self.__inScopePrefixes.get(namespace)
553            if pfxs:
554                prefix = next(iter(pfxs))
555        while prefix is None:
556            self.__namespacePrefixCounter += 1
557            candidate_prefix = 'ns%d' % (self.__namespacePrefixCounter,)
558            if not (candidate_prefix in self.__inScopeNamespaces):
559                prefix = candidate_prefix
560        ns = self.__inScopePrefixes.get(prefix)
561        if ns:
562            if ns != namespace:
563                raise pyxb.LogicError('Prefix %s is already in use for %s' % (prefix, ns))
564            return prefix
565        if not self.__mutableInScopeNamespaces:
566            self.__clonePrefixMap()
567            self.__mutableInScopeNamespaces = True
568        self.__addPrefixMap(prefix, namespace)
569        return prefix
570
571    def processXMLNS (self, prefix, uri):
572        from pyxb.namespace import builtin
573        if not self.__mutableInScopeNamespaces:
574            self.__clonePrefixMap()
575            self.__mutableInScopeNamespaces = True
576        if builtin.XML.boundPrefix() == prefix:
577            # Bound prefix xml is permitted if it's bound to the right URI, or
578            # if the scope is being left.  In neither case is the mapping
579            # adjusted.
580            if (uri is None) or builtin.XML.uri() == uri:
581                return
582            raise pyxb.LogicError('Cannot manipulate bound prefix xml')
583        if uri:
584            if prefix is None:
585                ns = self.__defaultNamespace = utility.NamespaceForURI(uri, create_if_missing=True)
586                self.__inScopeNamespaces[None] = self.__defaultNamespace
587            else:
588                ns = utility.NamespaceForURI(uri, create_if_missing=True)
589                self.__removePrefixMap(prefix)
590                self.__addPrefixMap(prefix, ns)
591            if self.__targetNamespace:
592                self.__targetNamespace._referenceNamespace(ns)
593            else:
594                self.__pendingReferencedNamespaces.add(ns)
595        else:
596            # NB: XMLNS 6.2 says that you can undefine a default
597            # namespace, but does not say anything explicitly about
598            # undefining a prefixed namespace.  XML-Infoset 2.2
599            # paragraph 6 implies you can do this, but expat blows up
600            # if you try it.  I don't think it's legal.
601            if prefix is not None:
602                raise pyxb.NamespaceError(self, 'Attempt to undefine non-default namespace %s' % (prefix,))
603            self.__removePrefixMap(prefix)
604            self.__defaultNamespace = None
605
606    def finalizeTargetNamespace (self, tns_uri=None, including_context=None):
607        if tns_uri is not None:
608            assert 0 < len(tns_uri)
609            # Do not prevent overwriting target namespace; need this for WSDL
610            # files where an embedded schema inadvertently inherits a target
611            # namespace from its enclosing definitions element.  Note that if
612            # we don't check this here, we do have to check it when schema
613            # documents are included into parent schema documents.
614            self.__targetNamespace = utility.NamespaceForURI(tns_uri, create_if_missing=True)
615        elif self.__targetNamespace is None:
616            if including_context is not None:
617                self.__targetNamespace = including_context.targetNamespace()
618                self.__fallbackToTargetNamespace = True
619            elif tns_uri is None:
620                self.__targetNamespace = utility.CreateAbsentNamespace()
621            else:
622                self.__targetNamespace = utility.NamespaceForURI(tns_uri, create_if_missing=True)
623        if self.__pendingReferencedNamespaces is not None:
624            [ self.__targetNamespace._referenceNamespace(_ns) for _ns in self.__pendingReferencedNamespaces ]
625            self.__pendingReferencedNamespace = None
626        assert self.__targetNamespace is not None
627        if (not self.__fallbackToTargetNamespace) and self.__targetNamespace.isAbsentNamespace():
628            self.__fallbackToTargetNamespace = True
629
630    def reset (self):
631        """Reset this instance to the state it was when created, exclusive of
632        XMLNS directives passed in a constructor C{dom_node} parameter.
633
634        This preserves parent context and constructor-specified prefix maps,
635        but clears the namespace-prefix mapping of any additions made while
636        processing namespace directives in DOM nodes, or manually added
637        post-construction.
638
639        The defaultNamespace is also retained."""
640        self.__inScopeNamespaces = self.__initialScopeNamespaces
641        self.__inScopePrefixes = self.__initialScopePrefixes
642        self.__mutableInScopeNamespaces = False
643        self.__namespacePrefixCounter = 0
644
645    def __init__ (self,
646                  dom_node=None,
647                  parent_context=None,
648                  including_context=None,
649                  recurse=True,
650                  default_namespace=None,
651                  target_namespace=None,
652                  in_scope_namespaces=None,
653                  expanded_name=None,
654                  finalize_target_namespace=True):  # MUST BE True for WSDL to work with minidom
655        """Determine the namespace context that should be associated with the
656        given node and, optionally, its element children.
657
658        Primarily this class maintains a map between namespaces and prefixes
659        used in QName instances.  The initial map comprises the bound prefixes
660        (C{xml} and C{xmlns}), prefixes inherited from C{parent_context}, and
661        prefixes passed through the C{in_scope_namespaces}
662        parameter to the constructor.  This map is then augmented by any
663        namespace declarations present in a passed C{dom_node}.  The initial
664        map prior to augmentation may be restored through the L{reset()}
665        method.
666
667        @param dom_node: The DOM node
668        @type dom_node: C{xml.dom.Element}
669        @keyword parent_context: Optional value that specifies the context
670        associated with C{dom_node}'s parent node.  If not provided, only the
671        C{xml} namespace is in scope.
672        @type parent_context: L{NamespaceContext}
673        @keyword recurse: If True (default), create namespace contexts for all
674        element children of C{dom_node}
675        @type recurse: C{bool}
676        @keyword default_namespace: Optional value to set as the default
677        namespace.  Values from C{parent_context} would override this, as
678        would an C{xmlns} attribute in the C{dom_node}.
679        @type default_namespace: L{NamespaceContext}
680        @keyword target_namespace: Optional value to set as the target
681        namespace.  Values from C{parent_context} would override this, as
682        would a C{targetNamespace} attribute in the C{dom_node}
683        @type target_namespace: L{NamespaceContext}
684        @keyword in_scope_namespaces: Optional value to set as the initial set
685        of in-scope namespaces.  The always-present namespaces are added to
686        this if necessary.
687        @type in_scope_namespaces: C{dict} mapping prefix C{string} to L{Namespace}.
688        """
689        from pyxb.namespace import builtin
690
691        if dom_node is not None:
692            try:
693                assert dom_node.__namespaceContext is None
694            except AttributeError:
695                pass
696            dom_node.__namespaceContext = self
697
698        self.__defaultNamespace = default_namespace
699        self.__targetNamespace = target_namespace
700        if self.__InitialScopeNamespaces is None:
701            self.__BuildInitialPrefixMap()
702        self.__inScopeNamespaces = self.__InitialScopeNamespaces
703        self.__inScopePrefixes = self.__InitialScopePrefixes
704        self.__mutableInScopeNamespaces = False
705        self.__namespacePrefixCounter = 0
706
707        if parent_context is not None:
708            self.__inScopeNamespaces = parent_context.__inScopeNamespaces
709            self.__inScopePrefixes = parent_context.__inScopePrefixes
710            if parent_context.__mutableInScopeNamespaces:
711                self.__clonePrefixMap()
712            self.__defaultNamespace = parent_context.defaultNamespace()
713            self.__targetNamespace = parent_context.targetNamespace()
714            self.__fallbackToTargetNamespace = parent_context.__fallbackToTargetNamespace
715        if in_scope_namespaces is not None:
716            self.__clonePrefixMap()
717            self.__mutableInScopeNamespaces = True
718            for (pfx, ns) in six.iteritems(in_scope_namespaces):
719                self.__removePrefixMap(pfx)
720                self.__addPrefixMap(pfx, ns)
721
722        # Record a copy of the initial mapping, exclusive of namespace
723        # directives from C{dom_node}, so we can reset to that state.
724        self.__initialScopeNamespaces = self.__inScopeNamespaces
725        self.__initialScopePrefixes = self.__inScopePrefixes
726        self.__mutableInScopeNamespaces = False
727
728        if self.__targetNamespace is None:
729            self.__pendingReferencedNamespaces = set()
730        attribute_map = {}
731        if dom_node is not None:
732            if expanded_name is None:
733                expanded_name = pyxb.namespace.ExpandedName(dom_node)
734            for ai in range(dom_node.attributes.length):
735                attr = dom_node.attributes.item(ai)
736                if builtin.XMLNamespaces.uri() == attr.namespaceURI:
737                    prefix = attr.localName
738                    if 'xmlns' == prefix:
739                        prefix = None
740                    self.processXMLNS(prefix, attr.value)
741                else:
742                    if attr.namespaceURI is not None:
743                        uri = utility.NamespaceForURI(attr.namespaceURI, create_if_missing=True)
744                        key = pyxb.namespace.ExpandedName(uri, attr.localName)
745                    else:
746                        key = pyxb.namespace.ExpandedName(None, attr.localName)
747                    attribute_map[key] = attr.value
748
749        if finalize_target_namespace:
750            tns_uri = None
751            tns_attr = self._TargetNamespaceAttribute(expanded_name)
752            if tns_attr is not None:
753                tns_uri = attribute_map.get(tns_attr)
754                self.finalizeTargetNamespace(tns_uri, including_context=including_context)
755
756        # Store in each node the in-scope namespaces at that node;
757        # we'll need them for QName interpretation of attribute
758        # values.
759        if (dom_node is not None) and recurse:
760            from xml.dom import Node
761            assert Node.ELEMENT_NODE == dom_node.nodeType
762            for cn in dom_node.childNodes:
763                if Node.ELEMENT_NODE == cn.nodeType:
764                    NamespaceContext(dom_node=cn, parent_context=self, recurse=True)
765
766    def interpretQName (self, name, namespace=None, default_no_namespace=False):
767        """Convert the provided name into an L{ExpandedName}, i.e. a tuple of
768        L{Namespace} and local name.
769
770        If the name includes a prefix, that prefix must map to an in-scope
771        namespace in this context.  Absence of a prefix maps to
772        L{defaultNamespace()}, which must be provided (or defaults to the
773        target namespace, if that is not absent).
774
775        @param name: A QName.
776        @type name: C{str} or C{unicode}
777        @param name: Optional namespace to use for unqualified names when
778        there is no default namespace.  Note that a defined default namespace,
779        even if absent, supersedes this value.
780        @keyword default_no_namespace: If C{False} (default), an NCName in a
781        context where C{namespace} is C{None} and no default or fallback
782        namespace can be identified produces an exception.  If C{True}, such an
783        NCName is implicitly placed in no namespace.
784        @return: An L{ExpandedName} tuple: ( L{Namespace}, C{str} )
785        @raise pyxb.QNameResolutionError: The prefix is not in scope
786        @raise pyxb.QNameResolutionError: No prefix is given and the default namespace is absent
787        """
788        if isinstance(name, pyxb.namespace.ExpandedName):
789            return name
790        assert isinstance(name, six.string_types)
791        if 0 <= name.find(':'):
792            (prefix, local_name) = name.split(':', 1)
793            assert self.inScopeNamespaces() is not None
794            namespace = self.inScopeNamespaces().get(prefix)
795            if namespace is None:
796                raise pyxb.QNameResolutionError('No namespace declaration for prefix', name, self)
797        else:
798            local_name = name
799            # Context default supersedes caller-provided namespace
800            if self.defaultNamespace() is not None:
801                namespace = self.defaultNamespace()
802            # If there's no default namespace, but there is a fallback
803            # namespace, use that instead.
804            if (namespace is None) and self.__fallbackToTargetNamespace:
805                namespace = self.targetNamespace()
806            if (namespace is None) and not default_no_namespace:
807                raise pyxb.QNameResolutionError('NCName with no fallback/default namespace cannot be resolved', name, self)
808        return pyxb.namespace.ExpandedName(namespace, local_name)
809
810    def queueForResolution (self, component, depends_on=None):
811        """Forwards to L{queueForResolution()<Namespace.queueForResolution>} in L{targetNamespace()}."""
812        assert isinstance(component, _Resolvable_mixin)
813        return self.targetNamespace().queueForResolution(component, depends_on)
814
815## Local Variables:
816## fill-column:78
817## End:
818