class XMLSchemaValueError(XMLSchemaException, ValueError):
    pass class XMLSchemaTypeError(XMLSchemaException, TypeError):
    pass XSD_ANNOTATION = _XSD_TEMPLATE % 'annotation' XSD_APPINFO = _XSD_TEMPLATE % 'appinfo' XSD_DOCUMENTATION = _XSD_TEMPLATE % 'documentation' XSD_ANY_TYPE = _XSD_TEMPLATE % 'anyType' XSD_ANY_SIMPLE_TYPE = _XSD_TEMPLATE % 'anySimpleType' XSD_ANY_ATOMIC_TYPE = _XSD_TEMPLATE % 'anyAtomicType' XSD_ID = _XSD_TEMPLATE % 'ID' XSD_QNAME = _XSD_TEMPLATE % 'QName' XSD_OVERRIDE = _XSD_TEMPLATE % 'override' XSD_NOTATION_TYPE = _XSD_TEMPLATE % 'NOTATION' XSD_DECIMAL = _XSD_TEMPLATE % 'decimal' _REGEX_NS_PREFIX = re.compile(r'ns\d+$')
class SafeXMLParser(PyElementTree.XMLParser):
    def __init__(self, target: Optional[Any] = None, encoding: Optional[str] = None) -> None:
    def entity_declaration(self, entity_name, is_parameter_entity, value, base,  # type: ignore
                           system_id, public_id, notation_name):
    def unparsed_entity_declaration(self, entity_name, base, system_id,  # type: ignore
                                    public_id, notation_name):
    def external_entity_reference(self, context, base, system_id, public_id):  # type: ignore
def is_etree_element(obj: Any) -> bool:
def etree_tostring(elem: etree_element,
                   namespaces: Optional[MutableMapping[str, str]] = None,
                   indent: str = '',
                   max_lines: Optional[int] = None,
                   spaces_for_tab: Optional[int] = None,
                   xml_declaration: Optional[bool] = None,
                   encoding: str = 'unicode',
                   method: str = 'xml') -> Union[str, bytes]:
    def reindent(line: str) -> str: C = TypeVar('C')
D = TypeVar('D')
E = TypeVar('E')
T = TypeVar('T') def get_qname(uri: Optional[str], name: str) -> str:
    """
    Returns an expanded QName from URI and local part. If any argument has boolean value
    `False` or if the name is already an expanded QName, returns the *name* argument.

    :param uri: namespace URI
    :param name: local or qualified name
    :return: string or the name argument
    """
    if not uri or not name or name[0] in ('{', '.', '/', '['):
        return name
    else:
        return '{%s}%s' % (uri, name) def local_name(qname: str) -> str:
    """
    Return the local part of an expanded QName or a prefixed name. If the name
    is `None` or empty returns the *name* argument.

    :param qname: an expanded QName or a prefixed name or a local name.
    """
    try:
        if qname[0] == '{':
            _, qname = qname.split('}')
        elif ':' in qname:
            _, qname = qname.split(':')
    except IndexError:
        return ''
    except ValueError:
        raise XMLSchemaValueError("the argument 'qname' has a wrong format: %r" % qname)
    except TypeError:
        raise XMLSchemaTypeError("the argument 'qname' must be a string")
    else:
        return qname def get_prefixed_qname(qname: str,
                       namespaces: Optional[MutableMapping[str, str]],
                       use_empty: bool = True) -> str:
    """
    Get the prefixed form of a QName, using a namespace map.

    :param qname: an extended QName or a local name or a prefixed QName.
    :param namespaces: an optional mapping from prefixes to namespace URIs.
    :param use_empty: if `True` use the empty prefix for mapping.
    """
    if not namespaces or not qname or qname[0] != '{':
        return qname

    namespace = get_namespace(qname)
    prefixes = [x for x in namespaces if namespaces[x] == namespace]

    if not prefixes:
        return qname
    elif prefixes[0]:
        return '%s:%s' % (prefixes[0], qname.split('}', 1)[1])
    elif len(prefixes) > 1:
        return '%s:%s' % (prefixes[1], qname.split('}', 1)[1])
    elif use_empty:
        return qname.split('}', 1)[1]
    else:
        return qname class XMLResource:
    """
    XML resource reader based on ElementTree and urllib.

    :param source: a string containing the XML document or file path or an URL or a \
    file like object or an ElementTree or an Element.
    :param base_url: is an optional base URL, used for the normalization of relative paths \
    when the URL of the resource can't be obtained from the source argument. For security \
    access to a local file resource is always denied if the *base_url* is a remote URL.
    :param allow: defines the security mode for accessing resource locations. Can be \
    'all', 'remote', 'local', 'sandbox' or 'none'. Default is 'all' that means all types of \
    URLs are allowed. With 'remote' only remote resource URLs are allowed. With 'local' \
    only file paths and URLs are allowed. With 'sandbox' only file paths and URLs that \
    are under the directory path identified by the *base_url* argument are allowed. \
    If you provide 'none' no located resource is allowed.
    :param defuse: defines when to defuse XML data using a `SafeXMLParser`. Can be \
    'always', 'remote', 'nonlocal' or 'never'. For default defuses only remote XML data. \
    With 'always' all the XML data that is not already parsed is defused. With 'nonlocal' \
    it defuses unparsed data except local files. With 'never' no XML data source is defused.
    :param timeout: the timeout in seconds for the connection attempt in case of remote data.
    :param lazy: if a value `False` or 0 is provided the XML data is fully loaded into and \
    processed from memory. For default only the root element of the source is loaded, \
    except in case the *source* argument is an Element or an ElementTree instance. A \
    positive integer also defines the depth at which the lazy resource can be better \
    iterated (`True` means 1).
    """
    # Protected attributes for data and resource location
    _source: XMLSourceType
    _root: ElementType
    _nsmap: Dict[ElementType, List[Tuple[str, str]]]
    _text: Optional[str] = None
    _url: Optional[str] = None
    _base_url: Optional[str] = None
    _parent_map: Optional[ParentMapType] = None
    _lazy: Union[bool, int] = False

    def __init__(self, source: XMLSourceType,
                 base_url: Union[None, str, Path, bytes] = None,
                 allow: str = 'all',
                 defuse: str = 'remote',
                 timeout: int = 300,
                 lazy: Union[bool, int] = False) -> None:

        if isinstance(base_url, str):
            if not is_url(base_url):
                raise XMLSchemaValueError("'base_url' argument is not an URL")
            self._base_url = base_url
        elif isinstance(base_url, Path):
            self._base_url = str(base_url)
        elif isinstance(base_url, bytes):
            if not is_url(base_url):
                raise XMLSchemaValueError("'base_url' argument is not an URL")
            self._base_url = base_url.decode()
        elif base_url is not None:
            msg = "invalid type {!r} for argument 'base_url'"
            raise XMLSchemaTypeError(msg.format(type(base_url)))

        if not isinstance(allow, str):
            msg = "invalid type {!r} for argument 'allow'"
            raise XMLSchemaTypeError(msg.format(type(allow)))
        elif allow not in SECURITY_MODES:
            msg = "'allow' argument: {!r} is not a security mode"
            raise XMLSchemaValueError(msg.format(allow))
        elif allow == 'sandbox' and self._base_url is None:
            msg = "block access to files out of sandbox requires 'base_url' to be set"
            raise XMLResourceError(msg)
        self._allow = allow

        if not isinstance(defuse, str):
            msg = "invalid type {!r} for argument 'defuse'"
            raise XMLSchemaTypeError(msg.format(type(defuse)))
        elif defuse not in DEFUSE_MODES:
            msg = "'defuse' argument: {!r} is not a defuse mode"
            raise XMLSchemaValueError(msg.format(defuse))
        self._defuse = defuse

        if not isinstance(timeout, int):
            msg = "invalid type {!r} for argument 'timeout'"
            raise XMLSchemaTypeError(msg.format(type(timeout)))
        elif timeout <= 0:
            msg = "the argument 'timeout' must be a positive integer"
            raise XMLSchemaValueError(msg)
        self._timeout = timeout

        self.parse(source, lazy)

    def __repr__(self) -> str:
        return '%s(root=%r)' % (self.__class__.__name__, self._root)

    @property
    def source(self) -> XMLSourceType:
        """The XML data source."""
        return self._source

    @property
    def root(self) -> ElementType:
        """The XML tree root Element."""
        return self._root

    @property
    def text(self) -> Optional[str]:
        """The XML text source, `None` if it's not available."""
        return self._text

    @property
    def name(self) -> Optional[str]:
        """
        The source name, is `None` if the instance is created from an Element or a string.
        """
        return None if self._url is None else os.path.basename(self._url)

    @property
    def url(self) -> Optional[str]:
        """
        The source URL, `None` if the instance is created from an Element or a string.
        """
        return self._url

    @property
    def base_url(self) -> Optional[str]:
        """The effective base URL used for completing relative locations."""
        return os.path.dirname(self._url) if self._url else self._base_url

    @property
    def filepath(self) -> Optional[str]:
        """
        The resource filepath if the instance is created from a local file, `None` otherwise.
        """
        if self._url:
            url_parts = urlsplit(self._url)
            if url_parts.scheme in ('', 'file'):
                return url_parts.path
        return None

    @property
    def allow(self) -> str:
        """The security mode for accessing resource locations."""
        return self._allow

    @property
    def defuse(self) -> str:
        """When to defuse XML data."""
        return self._defuse

    @property
    def timeout(self) -> int:
        """The timeout in seconds for accessing remote resources."""
        return self._timeout

    def _access_control(self, url: str) -> None:
        if self._allow == 'all':
            return
        elif self._allow == 'none':
            raise XMLResourceError("block access to resource {}".format(url))
        elif self._allow == 'remote':
            if is_local_url(url):
                raise XMLResourceError("block access to local resource {}".format(url))
        elif is_remote_url(url):
            raise XMLResourceError("block access to remote resource {}".format(url))
        elif self._allow == 'sandbox' and self._base_url is not None:
            if not url.startswith(normalize_url(self._base_url)):
                raise XMLResourceError("block access to out of sandbox file {}".format(url))

    def _update_nsmap(self, nsmap: MutableMapping[str, str], prefix: str, uri: str) -> None:
        if not prefix:
            if not uri:
                return
            elif '' not in nsmap:
                if self.namespace:
                    nsmap[prefix] = uri
                    return
            elif nsmap[''] == uri:
                return
            prefix = 'default'

        while prefix in nsmap:
            if nsmap[prefix] == uri:
                return
            match = re.search(r'(\d+)$', prefix)
            if match:
                index = int(match.group()) + 1
                prefix = prefix[:match.span()[0]] + str(index)
            else:
                prefix += '0'
        nsmap[prefix] = uri

    def _lazy_iterparse(self, resource: IO[AnyStr], nsmap: Optional[NsmapType] = None) \
            -> Iterator[Tuple[str, ElementType]]:
        events: Tuple[str, ...]
        _nsmap: List[Tuple[str, str]]

        if nsmap is None:
            events = 'start', 'end'
            _nsmap = []
        else:
            events = 'start-ns', 'end-ns', 'start', 'end'
            if isinstance(nsmap, list):
                _nsmap = nsmap
                _nsmap.clear()
            else:
                _nsmap = []

        if self._defuse == 'remote' and is_remote_url(self.base_url) \
                or self._defuse == 'nonlocal' and not is_local_url(self.base_url) \
                or self._defuse == 'always':
            safe_parser = SafeXMLParser(target=PyElementTree.TreeBuilder())
            tree_iterator = PyElementTree.iterparse(resource, events, safe_parser)
        else:
            tree_iterator = ElementTree.iterparse(resource, events)

        root_started = False
        nsmap_update = False

        _root = cast(Optional[ElementType], getattr(self, '_root', None))

        try:
            for event, node in tree_iterator:
                if event == 'start':
                    if not root_started:
                        self._root = node
                        root_started = True
                    if nsmap_update and isinstance(nsmap, dict):
                        for prefix, uri in _nsmap:
                            self._update_nsmap(nsmap, prefix, uri)
                        nsmap_update = False
                    yield event, node

                elif event == 'end':
                    yield event, node
                elif nsmap is not None:
                    if event == 'start-ns':
                        _nsmap.append(node)
                    else:
                        _nsmap.pop()
                    nsmap_update = isinstance(nsmap, dict)

        except Exception as err:
            if _root is not None:
                self._root = _root
            if isinstance(err, PyElementTree.ParseError):
                raise ElementTree.ParseError(str(err)) from None
            raise

    def _parse(self, resource: IO[AnyStr]) -> None:
        if self._defuse == 'remote' and is_remote_url(self.base_url) \
                or self._defuse == 'nonlocal' and not is_local_url(self.base_url) \
                or self._defuse == 'always':

            if not hasattr(resource, 'seekable') or not resource.seekable():
                text = resource.read()
                if isinstance(text, str):
                    resource = StringIO(text)
                else:
                    resource = BytesIO(text)

            safe_parser = SafeXMLParser(target=PyElementTree.TreeBuilder())
            try:
                for _ in PyElementTree.iterparse(resource, ('start',), safe_parser):
                    break
            except PyElementTree.ParseError as err:
                raise ElementTree.ParseError(str(err))
            else:
                resource.seek(0)

        elem: Optional[ElementType] = None
        nsmap: List[Tuple[str, str]] = []
        nsmap_changed = False
        namespaces = {}
        events = 'start-ns', 'end-ns', 'end'

        for event, node in ElementTree.iterparse(resource, events):
            if event == 'end':
                if nsmap_changed or elem is None:
                    namespaces[node] = nsmap[:]
                    nsmap_changed = False
                else:
                    namespaces[node] = namespaces[elem]
                elem = node
            elif event == 'start-ns':
                nsmap.append(node)
                nsmap_changed = True
            else:
                nsmap.pop()
                nsmap_changed = True

        assert elem is not None
        self._root = elem
        self._nsmap = namespaces

    def _parse_resource(self, resource: IO[AnyStr],
                        url: Optional[str],
                        lazy: Union[bool, int]) -> None:
        _url, self._url = self._url, url
        try:
            if not lazy:
                self._parse(resource)
            else:
                nsmap: List[Tuple[str, str]] = []
                for _, root in self._lazy_iterparse(resource, nsmap):  # pragma: no cover
                    self._nsmap = {root: nsmap}
                    break
        except Exception:
            self._url = _url
            raise

    def parse(self, source: XMLSourceType, lazy: Union[bool, int] = False) -> None:
        if isinstance(lazy, bool):
            pass
        elif not isinstance(lazy, int):
            msg = "invalid type {!r} for the attribute 'lazy'"
            raise XMLSchemaTypeError(msg.format(type(lazy)))
        elif lazy < 0:
            msg = "invalid value {!r} for the attribute 'lazy'"
            raise XMLSchemaValueError(msg.format(lazy))

        url: Optional[str]
        if isinstance(source, str):
            if is_url(source):
                # source is a string containing an URL or a file path
                url = normalize_url(source, self._base_url)
                self._access_control(url)

                with urlopen(url, timeout=self._timeout) as resource:
                    self._parse_resource(resource, url, lazy)

                self._text = None
                self._lazy = lazy
            else:
                # source is a string containing an XML document
                resource = StringIO(source)
                self._parse_resource(resource, None, lazy)
                self._text = source
                self._lazy = False

        elif isinstance(source, bytes):
            if is_url(source):
                url = normalize_url(source.decode(), self._base_url)
                self._access_control(url)

                with urlopen(url, timeout=self._timeout) as resource:
                    self._parse_resource(resource, url, lazy)

                self._text = None
                self._lazy = lazy

            else:
                resource = BytesIO(source)
                self._parse_resource(resource, None, lazy)
                self._text = source.decode()
                self._lazy = False

        elif isinstance(source, Path):
            url = normalize_url(str(source), self._base_url)
            self._access_control(url)

            with urlopen(url, timeout=self._timeout) as resource:
                self._parse_resource(resource, url, lazy)

            self._text = None
            self._lazy = lazy

        elif isinstance(source, StringIO):
            self._parse_resource(source, None, lazy)
            self._text = source.getvalue()
            self._lazy = lazy

        elif hasattr(source, 'read'):
            # source is a readable resource (remote or local file)
            url = getattr(source, 'url', None)
            if url is not None:
                self._access_control(url)

                # Save remote urls for open new resources (non seekable)
                if not is_remote_url(url):
                    url = None

            self._parse_resource(cast(IO[str], source), url, lazy)
            self._text = None
            self._lazy = lazy

        else:
            # Source is already an Element or an ElementTree.
            if hasattr(source, 'tag') and hasattr(source, 'attrib'):
                # Source is already an Element --> nothing to parse
                self._root = cast(ElementType, source)
            elif is_etree_document(source):
                # Could be only an ElementTree object at last
                self._root = cast(ElementTreeType, source).getroot()
            else:
                raise XMLSchemaTypeError(
                    "wrong type %r for 'source' attribute: an ElementTree object or "
                    "an Element instance or a string containing XML data or an URL "
                    "or a file-like object is required." % type(source)
                )

            self._text = self._url = None
            self._lazy = False
            self._nsmap = {}

            # TODO for Python 3.8+: need a Protocol for checking this with isinstance()
            if hasattr(self._root, 'nsmap'):
                nsmap = []
                lxml_nsmap = None
                for elem in cast(Any, self._root.iter()):
                    if lxml_nsmap != elem.nsmap:
                        lxml_nsmap = elem.nsmap
                        nsmap = [(k or '', v) for k, v in elem.nsmap.items()]
                    self._nsmap[elem] = nsmap

        self._parent_map = None
        self._source = source

    @property
    def namespace(self) -> str:
        """The namespace of the XML resource."""
        return '' if self._root is None else get_namespace(self._root.tag)

    @property
    def parent_map(self) -> Dict[ElementType, Optional[ElementType]]:
        if self._lazy:
            raise XMLResourceError("cannot create the parent map of a lazy resource")
        if self._parent_map is None:
            assert self._root is not None
            self._parent_map = {child: elem for elem in self._root.iter() for child in elem}
            self._parent_map[self._root] = None
        return self._parent_map

    def get_nsmap(self, elem: ElementType) -> List[Tuple[str, str]]:
        """
        Returns a list of couples with the namespace (nsmap) map of the element.
        Lazy resources have only a nsmap for the root element. If no nsmap is
        found for the element returns an empty list.
        """
        try:
            return self._nsmap[elem]
        except KeyError:
            return []

    def get_absolute_path(self, path: Optional[str] = None) -> str:
        if path is None:
            if self._lazy:
                return '/%s/%s' % (self._root.tag, '/'.join('*' * int(self._lazy)))
            return '/%s' % self._root.tag
        elif path.startswith('/'):
            return path
        else:
            return '/%s/%s' % (self._root.tag, path)

    def get_text(self) -> str:
        """
        Gets the source text of the XML document. If the source text is not
        available creates an encoded string representation of the XML tree.
        Il the resource is lazy raises a resource error.
        """
        if self._text is not None:
            return self._text
        elif self._url is not None:
            self.load()
            if self._text is not None:
                return self._text

        return self.tostring(xml_declaration=True)

    def tostring(self, indent: str = '', max_lines: Optional[int] = None,
                 spaces_for_tab: int = 4, xml_declaration: bool = False) -> str:
        """Generates a string representation of the XML resource."""
        if self._lazy:
            raise XMLResourceError("cannot serialize a lazy resource")

        elem = self._root
        namespaces = self.get_namespaces(root_only=False)
        _string = etree_tostring(elem, namespaces, indent, max_lines,
                                 spaces_for_tab, xml_declaration)

        return _string.decode('utf-8') if isinstance(_string, bytes) else _string

    def subresource(self, elem: ElementType) -> 'XMLResource':
        """Create an XMLResource instance from a subelement of a non-lazy XML tree."""
        if self._lazy:
            raise XMLResourceError("cannot create a subresource from a lazy resource")

        for e in self._root.iter():  # pragma: no cover
            if e is elem:
                break
        else:
            msg = "{!r} is not an element or the XML resource tree"
            raise XMLResourceError(msg.format(elem))

        resource = XMLResource(elem, self.base_url, self._allow, self._defuse, self._timeout)
        if not hasattr(elem, 'nsmap') and self._nsmap is not None:
            namespaces = {}
            _nsmap = self._nsmap[elem]
            _nsmap_initial_len = len(_nsmap)
            nsmap = list(dict(_nsmap).items())

            for e in elem.iter():
                if _nsmap is not self._nsmap[e]:
                    _nsmap = self._nsmap[e]
                    nsmap = nsmap[:]
                    nsmap.extend(_nsmap[_nsmap_initial_len:])
                namespaces[e] = nsmap

            resource._nsmap = namespaces

        return resource

    def open(self) -> IO[AnyStr]:
        """
        Returns a opened resource reader object for the instance URL. If the
        source attribute is a seekable file-like object rewind the source and
        return it.
        """
        if self.seek(0) == 0:
            return cast(IO[AnyStr], self._source)
        elif self._url is None:
            raise XMLResourceError("can't open, the resource has no URL associated.")

        try:
            return cast(IO[AnyStr], urlopen(self._url, timeout=self._timeout))
        except URLError as err:
            raise XMLResourceError(
                "cannot access to resource %r: %s" % (self._url, err.reason)
            )

    def seek(self, position: int) -> Optional[int]:
        """
        Change stream position if the XML resource was created with a seekable
        file-like object. In the other cases this method has no effect.
        """
        if not hasattr(self._source, 'read'):
            return None

        try:
            if not self._source.seekable():  # type: ignore[union-attr]
                return None
        except AttributeError:
            return None  # pragma: no cover
        except ValueError as err:
            raise XMLResourceError(str(err)) from None
        else:
            return self._source.seek(position)  # type: ignore[union-attr]

    def close(self) -> None:
        """
        Close the XML resource if it's created with a file-like object.
        In other cases this method has no effect.
        """
        try:
            self._source.close()  # type: ignore[union-attr]
        except (AttributeError, TypeError):
            pass

    def load(self) -> None:
        """
        Loads the XML text from the data source. If the data source is an Element
        the source XML text can't be retrieved.
        """
        if self._url is None and not hasattr(self._source, 'read'):
            return  # Created from Element or text source --> already loaded
        elif self._lazy:
            raise XMLResourceError("cannot load a lazy resource")

        resource = self.open()
        try:
            data = resource.read()
        finally:
            # We don't want to close the file obj if it wasn't originally
            # opened by `XMLResource`. That is the concern of the code
            # where the file obj came from.
            if resource is not self._source:
                resource.close()

        if isinstance(data, bytes):
            try:
                text = data.decode('utf-8')
            except UnicodeDecodeError:
                text = data.decode('iso-8859-1')
        else:
            text = data

        self._text = text

    def is_lazy(self) -> bool:
        """Returns `True` if the XML resource is lazy."""
        return bool(self._lazy)

    def is_remote(self) -> bool:
        """Returns `True` if the resource is related with remote XML data."""
        return is_remote_url(self._url)

    def is_local(self) -> bool:
        """Returns `True` if the resource is related with local XML data."""
        return is_local_url(self._url)

    @property
    def lazy_depth(self) -> int:
        """
        The optimal depth for validate this resource. Is a positive
        integer for lazy resources and 0 for fully loaded XML trees.
        """
        return int(self._lazy)

    def is_loaded(self) -> bool:
        """Returns `True` if the XML text of the data source is loaded."""
        return self._text is not None

    def iter(self, tag: Optional[str] = None, nsmap: Optional[NsmapType] = None) \
            -> Iterator[ElementType]:
        """
        XML resource tree iterator. The iteration of a lazy resource
        is in reverse order (top level element is the last). If tag
        is not None or '*', only elements whose tag equals tag are
        returned from the iterator. Provide a *nsmap* list for
        tracking the namespaces of yielded elements. If *nsmap* is
        a dictionary the tracking of namespaces is cumulative on
        the whole tree, renaming prefixes in case of conflicts.
        """
        if self._lazy:
            resource = self.open()
            tag = '*' if tag is None else tag.strip()
            try:
                for event, node in self._lazy_iterparse(resource, nsmap):
                    if event == 'end':
                        if tag == '*' or node.tag == tag:
                            yield node
                        node.clear()
            finally:
                # Close the resource only if it was originally opened by XMLResource
                if resource is not self._source:
                    resource.close()

        elif not self._nsmap or nsmap is None:
            yield from self._root.iter(tag)
        else:
            _nsmap = None
            for elem in self._root.iter(tag):
                try:
                    if _nsmap is not self._nsmap[elem]:
                        _nsmap = self._nsmap[elem]
                        if isinstance(nsmap, list):
                            nsmap.clear()
                            nsmap.extend(_nsmap)
                        else:
                            for prefix, uri in _nsmap:
                                self._update_nsmap(nsmap, prefix, uri)
                except KeyError:
                    pass

                yield elem

    def iter_location_hints(self, tag: Optional[str] = None) -> Iterator[Tuple[str, str]]:
        """
        Yields all schema location hints of the XML resource. If tag
        is not None or '*', only location hints of elements whose tag
        equals tag are returned from the iterator.
        """
        for elem in self.iter(tag):
            yield from etree_iter_location_hints(elem)

    def iter_depth(self, mode: int = 1, nsmap: Optional[NsmapType] = None,
                   ancestors: Optional[List[ElementType]] = None) -> Iterator[ElementType]:
        """
        Iterates XML subtrees. For fully loaded resources yields the root element.
        On lazy resources the argument *mode* can change the sequence and the
        completeness of yielded elements. There are four possible modes, that
        generate different sequences of elements:\n
          1. Only the elements at *depth_level* level of the tree\n
          2. Only a root element pruned at *depth_level*\n
          3. The elements at *depth_level* and then a pruned root\n
          4. An incomplete root at start, the elements at *depth_level* and a pruned root

        :param mode: an integer in range [1..4] that defines the iteration mode.
        :param nsmap: provide a list/dict for tracking the namespaces of yielded \
        elements. If a list is passed the tracking is done at element level, otherwise \
        the tracking is on the whole tree, renaming prefixes in case of conflicts.
        :param ancestors: provide a list for tracking the ancestors of yielded elements.
        """
        if ancestors is not None:
            ancestors.clear()

        if not self._lazy:
            if nsmap is not None and self._nsmap:
                if isinstance(nsmap, list):
                    nsmap.clear()
                    nsmap.extend(self._nsmap[self._root])
                else:
                    for elem in self._root.iter():
                        for prefix, uri in self._nsmap[elem]:
                            self._update_nsmap(nsmap, prefix, uri)

            yield self._root
            return

        if mode not in (1, 2, 3, 4):
            raise XMLSchemaValueError("invalid argument mode={!r}".format(mode))

        resource = self.open()
        level = 0
        subtree_level = int(self._lazy)

        try:
            for event, node in self._lazy_iterparse(resource, nsmap):
                if event == "start":
                    if not level:
                        if mode == 4:
                            yield node
                    if ancestors is not None and level < subtree_level:
                        ancestors.append(node)
                    level += 1
                else:
                    level -= 1
                    if not level:
                        if mode != 1:
                            yield node
                    elif level != subtree_level:
                        if ancestors is not None and level < subtree_level:
                            ancestors.pop()
                        continue  # pragma: no cover
                    elif mode != 2:
                        yield node

                    del node[:]  # delete children, keep attributes, text and tail.
        finally:
            if self._source is not resource:
                resource.close()

    def iterfind(self, path: str,
                 namespaces: Optional[NamespacesType] = None,
                 nsmap: Optional[NsmapType] = None,
                 ancestors: Optional[List[ElementType]] = None) -> Iterator[ElementType]:
        """
        Apply XPath selection to XML resource that yields full subtrees.

        :param path: an XPath expression to select element nodes.
        :param namespaces: an optional mapping from namespace prefixes to URIs \
        used for parsing the XPath expression.
        :param nsmap: provide a list/dict for tracking the namespaces of yielded \
        elements. If a list is passed the tracking is done at element level, otherwise \
        the tracking is on the whole tree, renaming prefixes in case of conflicts.
        :param ancestors: provide a list for tracking the ancestors of yielded elements.
        """
        selector: Any

        if self._lazy:
            selector = LazySelector(path, namespaces)
            path = path.replace(' ', '').replace('./', '')
            resource = self.open()
            level = 0
            select_all = '*' in path and set(path).issubset({'*', '/'})
            if path == '.':
                subtree_level = 0
            elif path.startswith('/'):
                subtree_level = path.count('/') - 1
            else:
                subtree_level = path.count('/') + 1

            try:
                for event, node in self._lazy_iterparse(resource, nsmap):
                    if event == "start":
                        if ancestors is not None and level < subtree_level:
                            ancestors.append(node)
                        level += 1
                    else:
                        level -= 1
                        if not level:
                            if subtree_level:
                                pass
                            elif select_all or node in selector.select(self._root):
                                yield node
                        elif not subtree_level:
                            continue
                        elif level != subtree_level:
                            if ancestors is not None and level < subtree_level:
                                ancestors.pop()
                            continue  # pragma: no cover
                        elif select_all or node in selector.select(self._root):
                            yield node

                        del node[:]  # delete children, keep attributes, text and tail.

            finally:
                if self._source is not resource:
                    resource.close()

        else:
            if ancestors is None:
                selector = iter_select
            else:
                parent_map = self.parent_map
                ancestors.clear()

                def selector(*args: Any, **kwargs: Any) -> Iterator[Any]:
                    assert ancestors is not None
                    for e in iter_select(*args, **kwargs):
                        if e is self._root:
                            ancestors.clear()
                        else:
                            _ancestors = []
                            parent = e
                            try:
                                while True:
                                    parent = parent_map[parent]
                                    if parent is not None:
                                        _ancestors.append(parent)
                            except KeyError:
                                pass

                            if _ancestors:
                                ancestors.clear()
                                ancestors.extend(reversed(_ancestors))

                        yield e

            if not self._nsmap or nsmap is None:
                yield from selector(self._root, path, namespaces, strict=False)
            else:
                _nsmap = None
                for elem in selector(self._root, path, namespaces, strict=False):
                    try:
                        if _nsmap is not self._nsmap[elem]:
                            _nsmap = self._nsmap[elem]
                            if isinstance(nsmap, list):
                                nsmap.clear()
                                nsmap.extend(_nsmap)
                            else:
                                for prefix, uri in _nsmap:
                                    self._update_nsmap(nsmap, prefix, uri)
                    except KeyError:
                        pass

                    yield elem

    def find(self, path: str,
             namespaces: Optional[NamespacesType] = None,
             nsmap: Optional[NsmapType] = None,
             ancestors: Optional[List[ElementType]] = None) -> Optional[ElementType]:
        return next(self.iterfind(path, namespaces, nsmap, ancestors), None)

    def findall(self, path: str, namespaces: Optional[NamespacesType] = None) \
            -> List[ElementType]:
        return list(self.iterfind(path, namespaces))

    def get_namespaces(self, namespaces: Optional[NamespacesType] = None,
                       root_only: Optional[bool] = None) -> NamespacesType:
        """
        Extracts namespaces with related prefixes from the XML resource. If a duplicate
        prefix declaration is encountered and the prefix maps a different namespace,
        adds the namespace using a different generated prefix. The empty prefix '' is
        used only if it's declared at root level to avoid erroneous mapping of local
        names. In other cases uses 'default' prefix as substitute.

        :param namespaces: builds the namespace map starting over the dictionary provided.
        :param root_only: if `True`, or `None` and the resource is lazy, extracts \
        only the namespaces declared in the root element.
        :return: a dictionary for mapping namespace prefixes to full URI.
        """
        if namespaces is None:
            namespaces = {}
        elif namespaces.get('xml', XML_NAMESPACE) != XML_NAMESPACE:
            msg = "reserved prefix (xml) must not be bound to another namespace name"
            raise XMLSchemaValueError(msg)
        else:
            namespaces = copy.copy(namespaces)

        try:
            if root_only or root_only is None and self._lazy:
                for _ in self.iter(nsmap=namespaces):
                    break
            else:
                for _ in self.iter(nsmap=namespaces):
                    pass
        except (ElementTree.ParseError, PyElementTree.ParseError, UnicodeEncodeError):
            pass

        return namespaces

    def get_locations(self, locations: Optional[LocationsType] = None,
                      root_only: Optional[bool] = None) -> NormalizedLocationsType:
        """
        Extracts a list of schema location hints from the XML resource.
        The locations are normalized using the base URL of the instance.

        :param locations: a sequence of schema location hints inserted \
        before the ones extracted from the XML resource. Locations passed \
        within a tuple container are not normalized.
        :param root_only: if `True`, or if `None` and the resource is lazy, \
        extracts the location hints of the root element only.
        :returns: a list of couples containing normalized location hints.
        """
        if root_only is None:
            root_only = bool(self._lazy)

        if not locations:
            location_hints = []
        elif isinstance(locations, tuple):
            location_hints = [x for x in locations]
        else:
            location_hints = normalize_locations(locations, self.base_url)

        if root_only:
            location_hints.extend([
                (ns, normalize_url(url, self.base_url))
                for ns, url in etree_iter_location_hints(self._root)
            ])
        else:
            location_hints.extend([
                (ns, normalize_url(url, self.base_url))
                for ns, url in self.iter_location_hints()
            ])
        return location_hints class XMLSchemaParseError(XMLSchemaValidatorError, SyntaxError):  # type: ignore[misc]
    """
    Raised when an error is found during the building of an XSD validator.

    :param validator: the XSD validator.
    :param message: the error message.
    :param elem: the element that contains the error.
    """
    def __init__(self, validator: 'XsdValidator', message: str,
                 elem: Optional[ElementType] = None) -> None:
        super(XMLSchemaParseError, self).__init__(
            validator=validator,
            message=message,
            elem=elem if elem is not None else getattr(validator, 'elem', None),
            source=getattr(validator, 'source', None),
            namespaces=getattr(validator, 'namespaces', None),
        ) class XMLSchemaValidationError(XMLSchemaValidatorError, ValueError):
    """
    Raised when the XML data is not validated with the XSD component or schema.
    It's used by decoding and encoding methods. Encoding validation errors do
    not include XML data element and source, so the error is limited to a message
    containing object representation and a reason.

    :param validator: the XSD validator.
    :param obj: the not validated XML data.
    :param reason: the detailed reason of failed validation.
    :param source: the XML resource that contains the error.
    :param namespaces: is an optional mapping from namespace prefix to URI.
    """
    def __init__(self,
                 validator: ValidatorType,
                 obj: Any,
                 reason: Optional[str] = None,
                 source: Optional['XMLResource'] = None,
                 namespaces: Optional[NamespacesType] = None) -> None:
        if not isinstance(obj, str):
            _obj = obj
        else:
            _obj = obj.encode('ascii', 'xmlcharrefreplace').decode('utf-8')

        super(XMLSchemaValidationError, self).__init__(
            validator=validator,
            message="failed validating {!r} with {!r}".format(_obj, validator),
            elem=obj if is_etree_element(obj) else None,
            source=source,
            namespaces=namespaces,
        )
        self.obj = obj
        self.reason = reason

    def __repr__(self) -> str:
        return '%s(reason=%r)' % (self.__class__.__name__, self.reason)

    def __str__(self) -> str:
        msg = ['%s:\n' % self.message]

        if self.reason is not None:
            msg.append('Reason: %s\n' % self.reason)

        if hasattr(self.validator, 'tostring'):
            chunk = self.validator.tostring('  ', 20)  # type: ignore[union-attr]
            msg.append("Schema:\n\n%s\n" % chunk)

        if self.elem is not None and is_etree_element(self.elem):
            try:
                elem_as_string = cast(str, etree_tostring(self.elem, self.namespaces, '  ', 20))
            except (ValueError, TypeError):        # pragma: no cover
                elem_as_string = repr(self.elem)   # pragma: no cover

            if hasattr(self.elem, 'sourceline'):
                line = getattr(self.elem, 'sourceline')
                msg.append("Instance (line %r):\n\n%s\n" % (line, elem_as_string))
            else:
                msg.append("Instance:\n\n%s\n" % elem_as_string)

        if self.path is not None:
            msg.append("Path: %s\n" % self.path)

        if len(msg) == 1:
            return msg[0][:-2]

        return '\n'.join(msg)
import re
import elementpath
from typing import TYPE_CHECKING, cast, Any, Dict, Generic, List, Iterator, Optional, \
    Set, Tuple, TypeVar, Union, MutableMapping
from ..exceptions import XMLSchemaValueError, XMLSchemaTypeError
from ..names import XSD_ANNOTATION, XSD_APPINFO, XSD_DOCUMENTATION, \
    XSD_ANY_TYPE, XSD_ANY_SIMPLE_TYPE, XSD_ANY_ATOMIC_TYPE, XSD_ID, \
    XSD_QNAME, XSD_OVERRIDE, XSD_NOTATION_TYPE, XSD_DECIMAL
from ..etree import is_etree_element, etree_tostring, etree_element
from ..aliases import ElementType, NamespacesType, SchemaType, BaseXsdType, \
    ComponentClassType, ExtraValidatorType, DecodeType, IterDecodeType, \
    EncodeType, IterEncodeType
from ..helpers import get_qname, local_name, get_prefixed_qname
from ..resources import XMLResource
from .exceptions import XMLSchemaParseError, XMLSchemaValidationError
    from .simple_types import XsdSimpleType
    from .complex_types import XsdComplexType
    from .elements import XsdElement
    from .groups import XsdGroup
    from .global_maps import XsdGlobals
                self.parse_error(msg.format(child, elem[:].index(e)), elem)
                break
            else:
                child = e
        return child

    def _parse_target_namespace(self) -> None:
        """
        XSD 1.1 targetNamespace attribute in elements and attributes declarations.
        """
        if 'targetNamespace' not in self.elem.attrib:
            return

        self._target_namespace = self.elem.attrib['targetNamespace'].strip()
        if 'name' not in self.elem.attrib:
            self.parse_error("attribute 'name' must be present when "
                             "'targetNamespace' attribute is provided")
        if 'form' in self.elem.attrib:
            self.parse_error("attribute 'form' must be absent when "
                             "'targetNamespace' attribute is provided")
        if self._target_namespace != self.schema.target_namespace:
            if self.parent is None:
                self.parse_error("a global %s must have the same namespace as "
                                 "its parent schema" % self.__class__.__name__)

            xsd_type = self.get_parent_type()
            if xsd_type is None or xsd_type.parent is not None:
                pass
            elif xsd_type.derivation != 'restriction' or \
                    getattr(xsd_type.base_type, 'name', None) == XSD_ANY_TYPE:
                self.parse_error("a declaration contained in a global complexType "
                                 "must have the same namespace as its parent schema")

        if self.name is None:
            pass  # pragma: no cover
        elif not self._target_namespace:
            self.name = local_name(self.name)
        else:
            self.name = '{%s}%s' % (self._target_namespace, local_name(self.name))

    @property
    def local_name(self) -> Optional[str]:
        """The local part of the name of the component, or `None` if the name is `None`."""
        return None if self.name is None else local_name(self.name)

    @property
    def qualified_name(self) -> Optional[str]:
        """The name of the component in extended format, or `None` if the name is `None`."""
        return None if self.name is None else get_qname(self.target_namespace, self.name)

    @property
    def prefixed_name(self) -> Optional[str]:
        """The name of the component in prefixed format, or `None` if the name is `None`."""
        return None if self.name is None else get_prefixed_qname(self.name, self.namespaces)

    @property
    def id(self) -> Optional[str]:
        """The ``'id'`` attribute of the component tag, ``None`` if missing."""
        return self.elem.get('id')

    @property
    def validation_attempted(self) -> str:
        return 'full' if self.built else 'partial'

    def build(self) -> None:
        """
        Builds components that are not fully parsed at initialization, like model groups
        or internal local elements in model groups. Otherwise does nothing.
        """

    @property
    def built(self) -> bool:
        raise NotImplementedError()

    def is_matching(self, name: Optional[str], default_namespace: Optional[str] = None,
                    **kwargs: Any) -> bool:
        """
        Returns `True` if the component name is matching the name provided as argument,
        `False` otherwise. For XSD elements the matching is extended to substitutes.

        :param name: a local or fully-qualified name.
        :param default_namespace: used if it's not None and not empty for completing \
        the name argument in case it's a local name.
        :param kwargs: additional options that can be used by certain components.
        """
        if not name:
            return self.name == name
        elif name[0] == '{':
            return self.qualified_name == name
        elif not default_namespace:
            return self.name == name or not self.qualified and self.local_name == name
        else:
            qname = '{%s}%s' % (default_namespace, name)
            return self.qualified_name == qname or not self.qualified and self.local_name == name

    def match(self, name: Optional[str], default_namespace: Optional[str] = None,
              **kwargs: Any) -> Optional['XsdComponent']:
        """
        Returns the component if its name is matching the name provided as argument,
        `None` otherwise.
        """
        return self if self.is_matching(name, default_namespace, **kwargs) else None

    def get_matching_item(self, mapping: MutableMapping[str, Any],
                          ns_prefix: str = 'xmlns',
                          match_local_name: bool = False) -> Optional[Any]:
        """
        If a key is matching component name, returns its value, otherwise returns `None`.
        """
        if self.name is None:
            return None
        elif not self.target_namespace:
            return mapping.get(self.name)
        elif self.qualified_name in mapping:
            return mapping[cast(str, self.qualified_name)]
        elif self.prefixed_name in mapping:
            return mapping[cast(str, self.prefixed_name)]

        # Try a match with other prefixes
        target_namespace = self.target_namespace

        suffix = ':%s' % self.local_name