elementpath/xpath_nodes.py
class TypedAttribute(XPathNode):
    """
    A class for processing typed attribute nodes.

    :param attribute: the origin AttributeNode instance.
    :param xsd_type: the reference XSD type.
    :param value: the types value.
    """
    def __init__(self, attribute: AttributeNode, xsd_type: Any, value: Any) -> None:
        self.attribute = attribute
        self.xsd_type = xsd_type
        self.value = value
        self.parent = attribute.parent

    @property
    def kind(self) -> str:
        return 'attribute'

    @property
    def name(self) -> str:
        return self.attribute.name

    def as_item(self) -> Tuple[str, Any]:
        return self.attribute.name, self.value

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

    def __eq__(self, other: Any) -> bool:
        return isinstance(other, self.__class__) and \
            self.attribute == other.attribute and \
            self.value == other.value

    def __hash__(self) -> int:
        return hash((self.attribute, self.value))
elementpath/xpath_nodes.py
def is_document_node(obj: Any) -> bool:
    return hasattr(obj, 'getroot') and hasattr(obj, 'parse') and hasattr(obj, 'iter')
elementpath/helpers.py
def collapse_white_spaces(s: str) -> str:
    return WHITESPACES_PATTERN.sub(' ', s).strip(' ')
elementpath/xpath_nodes.py
def is_schema_node(obj: Any) -> bool:
    return hasattr(obj, 'local_name') and hasattr(obj, 'type') and hasattr(obj, 'name')
elementpath/xpath_nodes.py
class AttributeNode(XPathNode):
    """
    A class for processing XPath attribute nodes.

    :param name: the attribute name.
    :param value: a string value or an XSD attribute when XPath is applied on a schema.
    :param parent: the parent element.
    """
    name: str

    def __init__(self, name: str, value: Union[str, XsdAttributeProtocol],
                 parent: Optional[ElementNode] = None) -> None:
        self.name = name
        self.value: Union[str, XsdAttributeProtocol] = value
        self.parent = parent

    @property
    def kind(self) -> str:
        return 'attribute'

    def as_item(self) -> Tuple[str, Union[str, XsdAttributeProtocol]]:
        return self.name, self.value

    def __repr__(self) -> str:
        if self.parent is not None:
            return '%s(name=%r, value=%r, parent=%r)' % (
                self.__class__.__name__, self.name, self.value, self.parent
            )
        return '%s(name=%r, value=%r)' % (self.__class__.__name__, self.name, self.value)

    def __eq__(self, other: Any) -> bool:
        return isinstance(other, self.__class__) and \
            self.name == other.name and \
            self.value == other.value and \
            self.parent is other.parent

    def __hash__(self) -> int:
        return hash((self.name, self.value, self.parent))
elementpath/schema_proxy.py
class AbstractSchemaProxy(metaclass=ABCMeta):
    """
    Abstract base class for defining schema proxies.

    :param schema: a schema instance that implements the `AbstractEtreeElement` interface.
    :param base_element: the schema element used as base item for static analysis.
    """
    def __init__(self, schema: XMLSchemaProtocol,
                 base_element: Optional[ElementProtocol] = None) -> None:
        if not is_etree_element(schema):
            raise ElementPathTypeError(
                "argument {!r} is not a compatible schema instance".format(schema)
            )
        if base_element is not None and not is_etree_element(base_element):
            raise ElementPathTypeError(
                "argument 'base_element' is not a compatible element instance"
            )

        self._schema = schema
        self._base_element: Optional[ElementProtocol] = base_element

    def bind_parser(self, parser: XPathParserType) -> None:
        """
        Binds a parser instance with schema proxy adding the schema's atomic types constructors.
        This method can be redefined in a concrete proxy to optimize schema bindings.

        :param parser: a parser instance.
        """
        if parser.schema is not self:
            parser.schema = self

        parser.symbol_table = dict(parser.__class__.symbol_table)
        for xsd_type in self.iter_atomic_types():
            if xsd_type.name is not None:
                parser.schema_constructor(xsd_type.name)
        parser.tokenizer = parser.create_tokenizer(parser.symbol_table)

    def get_context(self) -> XPathSchemaContext:
        """
        Get a context instance for static analysis phase.

        :returns: an `XPathSchemaContext` instance.
        """
        return XPathSchemaContext(root=self._schema, item=self._base_element)

    def find(self, path: str, namespaces: Optional[Dict[str, str]] = None) \
            -> Optional[XsdElementProtocol]:
        """
        Find a schema element or attribute using an XPath expression.

        :param path: an XPath expression that selects an element or an attribute node.
        :param namespaces: an optional mapping from namespace prefix to namespace URI.
        :return: The first matching schema component, or ``None`` if there is no match.
        """
        return cast(Optional[XsdElementProtocol], self._schema.find(path, namespaces))

    @property
    def xsd_version(self) -> str:
        """The XSD version, returns '1.0' or '1.1'."""
        return self._schema.xsd_version

    def get_type(self, qname: str) -> Optional[XsdTypeProtocol]:
        """
        Get the XSD global type from the schema's scope. A concrete implementation must
        return an object that supports the protocols `XsdTypeProtocol`, or `None` if
        the global type is not found.

        :param qname: the fully qualified name of the type to retrieve.
        :returns: an object that represents an XSD type or `None`.
        """
        return self._schema.maps.types.get(qname)

    def get_attribute(self, qname: str) -> Optional[XsdAttributeProtocol]:
        """
        Get the XSD global attribute from the schema's scope. A concrete implementation must
        return an object that supports the protocol `XsdAttributeProtocol`, or `None` if
        the global attribute is not found.

        :param qname: the fully qualified name of the attribute to retrieve.
        :returns: an object that represents an XSD attribute or `None`.
        """
        return self._schema.maps.attributes.get(qname)

    def get_element(self, qname: str) -> Optional[XsdElementProtocol]:
        """
        Get the XSD global element from the schema's scope. A concrete implementation must
        return an object that supports the protocol `XsdElementProtocol` interface, or
        `None` if the global element is not found.

        :param qname: the fully qualified name of the element to retrieve.
        :returns: an object that represents an XSD element or `None`.
        """
        return self._schema.maps.elements.get(qname)

    def get_substitution_group(self, qname: str) -> Optional[List[XsdElementProtocol]]:
        """
        Get a substitution group. A concrete implementation must returns a list containing
        substitution elements or `None` if the substitution group is not found. Moreover each item
        of the returned list must be an object that implements the `AbstractXsdElement` interface.

        :param qname: the fully qualified name of the substitution group to retrieve.
        :returns: a list containing substitution elements or `None`.
        """
        return self._schema.maps.substitution_groups.get(qname)

    @abstractmethod
    def is_instance(self, obj: Any, type_qname: str) -> bool:
        """
        Returns `True` if *obj* is an instance of the XSD global type, `False` if not.

        :param obj: the instance to be tested.
        :param type_qname: the fully qualified name of the type used to test the instance.
        """

    @abstractmethod
    def cast_as(self, obj: Any, type_qname: str) -> AtomicValueType:
        """
        Converts *obj* to the Python type associated with an XSD global type. A concrete
        implementation must raises a `ValueError` or `TypeError` in case of a decoding
        error or a `KeyError` if the type is not bound to the schema's scope.

        :param obj: the instance to be casted.
        :param type_qname: the fully qualified name of the type used to convert the instance.
        """

    @abstractmethod
    def iter_atomic_types(self) -> Iterator[XsdTypeProtocol]:
        """
        Returns an iterator for not builtin atomic types defined in the schema's scope. A concrete
        implementation must yields objects that implement the protocol `XsdTypeProtocol`.
        """

    @abstractmethod
    def get_primitive_type(self, xsd_type: XsdTypeProtocol) -> XsdTypeProtocol:
        """
        Returns the type at base of the definition of an XSD type. For an atomic type
        is effectively the primitive type. For a list is the primitive type of the item.
        For a union is the base union type. For a complex type is xs:anyType.

        :param xsd_type: an XSD type instance.
        :return: an XSD type instance.
        """
elementpath/xpath1/xpath1_parser.py
class XPath1Parser(Parser[XPathToken]):
    """
    XPath 1.0 expression parser class. Provide a *namespaces* dictionary argument for
    mapping namespace prefixes to URI inside expressions. If *strict* is set to `False`
    the parser enables also the parsing of QNames, like the ElementPath library.

    :param namespaces: a dictionary with mapping from namespace prefixes into URIs.
    :param strict: a strict mode is `False` the parser enables parsing of QNames \
    in extended format, like the Python's ElementPath library. Default is `True`.
    """
    version = '1.0'
    """The XPath version string."""

    token_base_class = XPathToken
    literals_pattern = re.compile(
        r"""'(?:[^']|'')*'|"(?:[^"]|"")*"|(?:\d+|\.\d+)(?:\.\d*)?(?:[Ee][+-]?\d+)?"""
    )
    name_pattern = re.compile(r'[^\d\W][\w.\-\xb7\u0300-\u036F\u203F\u2040]*')

    SYMBOLS: ClassVar[FrozenSet[str]] = Parser.SYMBOLS | {
        # Axes
        'descendant-or-self', 'following-sibling', 'preceding-sibling',
        'ancestor-or-self', 'descendant', 'attribute', 'following',
        'namespace', 'preceding', 'ancestor', 'parent', 'child', 'self',

        # Operators
        'and', 'mod', 'div', 'or', '..', '//', '!=', '<=', '>=', '(', ')', '[', ']',
        ':', '.', '@', ',', '/', '|', '*', '-', '=', '+', '<', '>', '$', '::',

        # Node test functions
        'node', 'text', 'comment', 'processing-instruction',

        # Node set functions
        'last', 'position', 'count', 'id', 'name', 'local-name', 'namespace-uri',

        # String functions
        'string', 'concat', 'starts-with', 'contains',
        'substring-before', 'substring-after', 'substring',
        'string-length', 'normalize-space', 'translate',

        # Boolean functions
        'boolean', 'not', 'true', 'false', 'lang',

        # Number functions
        'number', 'sum', 'floor', 'ceiling', 'round',

        # Symbols for ElementPath extensions
        '{', '}'
    }

    DEFAULT_NAMESPACES: ClassVar[Dict[str, str]] = {'xml': XML_NAMESPACE}
    """
    The default prefix-to-namespace associations of the XPath class. These namespaces
    are updated in the instance with the ones passed with the *namespaces* argument.
    """

    # Labels and symbols admitted after a path step
    PATH_STEP_LABELS: ClassVar[Tuple[str, ...]] = ('axis', 'kind test')
    PATH_STEP_SYMBOLS: ClassVar[Set[str]] = {
        '(integer)', '(string)', '(float)', '(decimal)', '(name)', '*', '@', '..', '.', '{'
    }

    # Class attributes for compatibility with XPath 2.0+
    schema: Optional[AbstractSchemaProxy] = None
    variable_types: Optional[Dict[str, str]] = None
    base_uri: Optional[str] = None
    function_namespace = XPATH_FUNCTIONS_NAMESPACE
    function_signatures: Dict[Tuple[QName, int], str] = {}

    RESERVED_FUNCTION_NAMES = {
        'comment', 'element', 'node', 'processing-instruction', 'text'
    }

    def __init__(self, namespaces: Optional[NamespacesType] = None, strict: bool = True,
                 *args: Any, **kwargs: Any) -> None:
        super(XPath1Parser, self).__init__()
        self.namespaces: Dict[str, str] = self.DEFAULT_NAMESPACES.copy()
        if namespaces is not None:
            self.namespaces.update(namespaces)
        self.strict: bool = strict

    @property
    def compatibility_mode(self) -> bool:
        """XPath 1.0 compatibility mode."""
        return True

    @property
    def default_namespace(self) -> Optional[str]:
        """
        The default namespace. For XPath 1.0 this value is always `None` because the default
        namespace is ignored (see https://www.w3.org/TR/1999/REC-xpath-19991116/#node-tests).
        """
        return None

    @property
    def other_namespaces(self) -> Dict[str, str]:
        """The subset of namespaces not provided by default."""
        return {k: v for k, v in self.namespaces.items() if k not in self.DEFAULT_NAMESPACES}

    @property
    def xsd_version(self) -> str:
        return '1.0'  # Use XSD 1.0 datatypes for default

    def xsd_qname(self, local_name: str) -> str:
        """Returns a prefixed QName string for XSD namespace."""
        if self.namespaces.get('xs') == XSD_NAMESPACE:
            return 'xs:%s' % local_name

        for pfx, uri in self.namespaces.items():
            if uri == XSD_NAMESPACE:
                return '%s:%s' % (pfx, local_name) if pfx else local_name

        raise xpath_error('XPST0081', 'Missing XSD namespace registration')

    @staticmethod
    def unescape(string_literal: str) -> str:
        if string_literal.startswith("'"):
            return string_literal[1:-1].replace("''", "'")
        else:
            return string_literal[1:-1].replace('""', '"')

    @classmethod
    def axis(cls, symbol: str, reverse_axis: bool = False, bp: int = 80) -> Type[XPathAxis]:
        """Register a token for a symbol that represents an XPath *axis*."""
        token_class = cls.register(symbol, label='axis', bases=(XPathAxis,),
                                   reverse_axis=reverse_axis, lbp=bp, rbp=bp)
        return cast(Type[XPathAxis], token_class)

    @classmethod
    def function(cls, symbol: str,
                 nargs: NargsType = None,
                 sequence_types: Tuple[str, ...] = (),
                 label: str = 'function',
                 bp: int = 90) -> Type[XPathFunction]:
        """
        Registers a token class for a symbol that represents an XPath function.
        """
        if 'function' not in label:
            pass  # kind test or sequence type
        elif symbol in cls.RESERVED_FUNCTION_NAMES:
            raise ElementPathValueError(f'{symbol!r} is a reserved function name')
        elif sequence_types:
            # Register function signature(s)
            if label == 'math function':
                qname = QName(XPATH_MATH_FUNCTIONS_NAMESPACE, 'math:%s' % symbol)
            else:
                qname = QName(XPATH_FUNCTIONS_NAMESPACE, 'fn:%s' % symbol)

            if nargs is None:
                pass  # pragma: no cover
            elif isinstance(nargs, int):
                assert len(sequence_types) == nargs + 1
                cls.function_signatures[(qname, nargs)] = 'function({}) as {}'.format(
                    ', '.join(sequence_types[:-1]), sequence_types[-1]
                )
            elif nargs[1] is None:
                assert len(sequence_types) == nargs[0] + 1
                cls.function_signatures[(qname, nargs[0])] = 'function({}, ...) as {}'.format(
                    ', '.join(sequence_types[:-1]), sequence_types[-1]
                )
            else:
                assert len(sequence_types) == nargs[1] + 1
                for arity in range(nargs[0], nargs[1] + 1):
                    cls.function_signatures[(qname, arity)] = 'function({}) as {}'.format(
                        ', '.join(sequence_types[:arity]), sequence_types[-1]
                    )

        token_class = cls.register(symbol, nargs=nargs, sequence_types=sequence_types,
                                   label=label, bases=(XPathFunction,), lbp=bp, rbp=bp)
        return cast(Type[XPathFunction], token_class)

    def parse(self, source: str) -> XPathToken:
        root_token = super(XPath1Parser, self).parse(source)
        try:
            root_token.evaluate()  # Static context evaluation
        except MissingContextError:
            pass
        return root_token

    def expected_name(self, *symbols: str, message: Optional[str] = None) -> None:
        """
        Checks the next symbol with a list of symbols. Replaces the next token
        with a '(name)' token if check fails and the symbol can be also a name.
        Otherwise raises a syntax error.

        :param symbols: a sequence of symbols.
        :param message: optional error message.
        """
        if self.next_token.symbol in symbols:
            return
        elif self.next_token.label in ('operator', 'symbol', 'let expression') and \
                self.name_pattern.match(self.next_token.symbol) is not None:
            token_class = self.symbol_table['(name)']
            self.next_token = token_class(self, self.next_token.symbol)
        else:
            raise self.next_token.wrong_syntax(message)

    ###
    # Type checking (used in XPath 2.0)
    def is_instance(self, obj: Any, type_qname: str) -> bool:
        """Checks an instance against an XSD type."""
        if get_namespace(type_qname) == XSD_NAMESPACE:
            if type_qname == XSD_ERROR:
                return obj is None or obj == []
            elif type_qname == XSD_UNTYPED_ATOMIC:
                return isinstance(obj, UntypedAtomic)
            elif type_qname == XSD_ANY_ATOMIC_TYPE:
                return isinstance(obj, AnyAtomicType)
            elif type_qname == XSD_ANY_SIMPLE_TYPE:
                return isinstance(obj, AnyAtomicType) or \
                    isinstance(obj, list) and \
                    all(isinstance(x, AnyAtomicType) for x in obj)

            try:
                if self.xsd_version == '1.1':
                    return isinstance(obj, xsd11_atomic_types[type_qname])
                return isinstance(obj, xsd10_atomic_types[type_qname])
            except KeyError:
                pass

        if self.schema is not None:
            try:
                return self.schema.is_instance(obj, type_qname)
            except KeyError:
                pass

        raise ElementPathKeyError("unknown type %r" % type_qname)

    def is_sequence_type(self, value: str) -> bool:
        """Checks if a string is a sequence type specification."""
        try:
            value = normalize_sequence_type(value)
        except TypeError:
            return False

        if not value:
            return False
        elif value == 'empty-sequence()' or value == 'none':
            return True
        elif value[-1] in OCCURRENCE_INDICATORS:
            value = value[:-1]

        if value in COMMON_SEQUENCE_TYPES:
            return True

        elif value.startswith('element(') and value.endswith(')'):
            if ',' not in value:
                return EQNAME_PATTERN.match(value[8:-1]) is not None

            try:
                arg1, arg2 = value[8:-1].split(', ')
            except ValueError:
                return False
            else:
                return (arg1 == '*' or EQNAME_PATTERN.match(arg1) is not None) \
                    and EQNAME_PATTERN.match(arg2) is not None

        elif value.startswith('document-node(') and value.endswith(')'):
            if not value.startswith('document-node(element('):
                return False
            return self.is_sequence_type(value[14:-1])

        elif value.startswith('function('):
            if self.version >= '3.0':
                if value == 'function(*)':
                    return True
                elif ' as ' in value:
                    pass
                elif not value.endswith(')'):
                    return False
                else:
                    return self.is_sequence_type(value[9:-1])

            try:
                value, return_type = value.rsplit(' as ', 1)
            except ValueError:
                return False
            else:
                if not self.is_sequence_type(return_type):
                    return False
                elif value == 'function()':
                    return True

                value = value[9:-1]
                if value.endswith(', ...'):
                    value = value[:-5]

                if 'function(' not in value:
                    return all(self.is_sequence_type(x) for x in value.split(', '))

                # Cover only if function() spec is the last argument
                k = value.index('function(')
                if not self.is_sequence_type(value[k:]):
                    return False
                return all(self.is_sequence_type(x) for x in value[:k].split(', ') if x)

        elif QName.pattern.match(value) is None:
            return False

        try:
            type_qname = get_expanded_name(value, self.namespaces)
            self.is_instance(None, type_qname)
        except (KeyError, ValueError):
            return False
        else:
            return True

    def get_atomic_value(self, type_or_name: Union[str, XsdTypeProtocol]) -> AtomicValueType:
        """Gets an atomic value for an XSD type instance or name. Used for schema contexts."""
        expanded_name: Optional[str]

        if isinstance(type_or_name, str):
            expanded_name = get_expanded_name(type_or_name, self.namespaces)
            xsd_type = None
        else:
            xsd_type = type_or_name
            expanded_name = xsd_type.name

        if expanded_name:
            uri, local_name = split_expanded_name(expanded_name)
            if uri == XSD_NAMESPACE:
                try:
                    return ATOMIC_VALUES[local_name]
                except KeyError:
                    pass

        if xsd_type is None and self.schema is not None:
            xsd_type = self.schema.get_type(expanded_name or '')

        if xsd_type is None:
            return UntypedAtomic('1')
        elif xsd_type.is_simple() or xsd_type.has_simple_content():
            if self.schema is None:
                return UntypedAtomic('1')
            try:
                primitive_type = self.schema.get_primitive_type(xsd_type)
                return ATOMIC_VALUES[cast(str, primitive_type.local_name)]
            except (KeyError, AttributeError):
                return UntypedAtomic('1')
        else:
            # returns an xs:untypedAtomic value also for element-only types
            # (that should be None) because it is for static evaluation.
            return UntypedAtomic('1')

    def match_sequence_type(self, value: Any,
                            sequence_type: str,
                            occurrence: Optional[str] = None) -> bool:
        """
        Checks a value instance against a sequence type.

        :param value: the instance to check.
        :param sequence_type: a string containing the sequence type spec.
        :param occurrence: an optional occurrence spec, can be '?', '+' or '*'.
        """
        if sequence_type[-1] in OCCURRENCE_INDICATORS:
            return self.match_sequence_type(value, sequence_type[:-1], sequence_type[-1])
        elif value is None or isinstance(value, list) and value == []:
            return sequence_type in ('empty-sequence()', 'none') or occurrence in ('?', '*')
        elif sequence_type in ('empty-sequence()', 'none'):
            return False
        elif isinstance(value, list):
            if len(value) == 1:
                return self.match_sequence_type(value[0], sequence_type)
            elif occurrence is None or occurrence == '?':
                return False
            else:
                return all(self.match_sequence_type(x, sequence_type) for x in value)
        elif sequence_type == 'item()':
            return is_xpath_node(value) or isinstance(value, (AnyAtomicType, list, XPathFunction))
        elif sequence_type == 'numeric':
            return isinstance(value, NumericProxy)
        elif sequence_type.startswith('function('):
            if not isinstance(value, XPathFunction):
                return False
            return value.match_function_test(sequence_type)

        value_kind = node_kind(value)
        if value_kind is None:
            try:
                type_expanded_name = get_expanded_name(sequence_type, self.namespaces)
                return self.is_instance(value, type_expanded_name)
            except (KeyError, ValueError):
                return False
        elif sequence_type == 'node()':
            return True
        elif not sequence_type.startswith(value_kind) or not sequence_type.endswith(')'):
            return False
        elif sequence_type == f'{value_kind}()':
            return True
        elif value_kind == 'document-node':
            return self.match_sequence_type(value.getroot(), sequence_type[14:-1])
        elif value_kind not in ('element', 'attribute'):
            return False

        _, params = sequence_type[:-1].split('(')
        if ',' not in sequence_type:
            name = params
        else:
            name, type_name = params.split(',')
            if type_name.endswith('?'):
                type_name = type_name[:-1]
            elif node_nilled(value):
                return False

            if type_name == 'xs:untyped':
                if isinstance(value, (TypedAttribute, TypedElement)):
                    return False
            else:
                try:
                    type_expanded_name = get_expanded_name(type_name, self.namespaces)
                    if not self.is_instance(value, type_expanded_name):
                        return False
                except (KeyError, ValueError):
                    return False

        if name == '*':
            return True

        try:
            return node_name(value) == get_expanded_name(name, self.namespaces)
        except (KeyError, ValueError):
            return False

    def check_variables(self, values: MutableMapping[str, Any]) -> None:
        """Checks the sequence types of the XPath dynamic context's variables."""
        for varname, value in values.items():
            if not self.match_sequence_type(
                    value, 'item()', occurrence='*' if isinstance(value, list) else None):
                message = "Unmatched sequence type for variable {!r}".format(varname)
                raise xpath_error('XPDY0050', message)
elementpath/datatypes/datetime.py
class DayTimeDuration(Duration):

    name = 'dayTimeDuration'

    def __init__(self, seconds: Union[Decimal, int] = 0) -> None:
        super(DayTimeDuration, self).__init__(0, seconds)

    @classmethod
    def fromtimedelta(cls, td: datetime.timedelta) -> 'DayTimeDuration':
        return cls(seconds=Decimal(
            '{}.{:06}'.format(td.days * 86400 + td.seconds, td.microseconds)
        ))

    def get_timedelta(self) -> datetime.timedelta:
        return datetime.timedelta(
            seconds=int(self.seconds), microseconds=int(self.seconds % 1 * 1000000)
        )

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

    def __add__(self, other: object) -> Union['DayTimeDuration', Time, OrderedDateTime]:
        if isinstance(other, (Time, Date10)):
            return other + self
        elif isinstance(other, self.__class__):
            return DayTimeDuration(self.seconds + other.seconds)
        raise TypeError("cannot add %r to %r" % (type(other), type(self)))

    def __sub__(self, other: object) -> 'DayTimeDuration':
        if not isinstance(other, self.__class__):
            raise TypeError("cannot subtract %r from %r" % (type(other), type(self)))
        return DayTimeDuration(seconds=self.seconds - other.seconds)

    def __mul__(self, other: object) -> 'DayTimeDuration':
        if isinstance(other, (float, int, Decimal)):
            if math.isnan(other):
                raise ValueError("cannot multiply a %r by NaN" % type(self))

            if isinstance(other, (int, Decimal)):
                seconds = self.seconds * other
            else:
                seconds = self.seconds * Decimal.from_float(other)

            return DayTimeDuration(seconds)
        else:
            raise TypeError("cannot multiply a %r by %r" % (type(self), type(other)))

    def __truediv__(self, other: object) -> Union[Decimal, 'DayTimeDuration']:
        if isinstance(other, self.__class__):
            return self.seconds / other.seconds
        elif isinstance(other, (float, int, Decimal)):
            if math.isnan(other):
                raise ValueError("cannot divide a %r by NaN" % type(self))

            if isinstance(other, (int, Decimal)):
                seconds = self.seconds / other
            else:
                seconds = self.seconds / Decimal.from_float(other)

            return DayTimeDuration(seconds)
        else:
            raise TypeError("cannot divide a %r by %r" % (type(self), type(other)))
elementpath/namespaces.py
XSD_NAMESPACE = "http://www.w3.org/2001/XMLSchema"
elementpath/datatypes/uri.py
class AnyURI(AnyAtomicType):
    """
    Class for xs:anyURI data.

    :param value: a string or an untyped atomic instance.
    """
    value: str
    name = 'anyURI'

    def __init__(self, value: Union[str, bytes, UntypedAtomic, 'AnyURI']) -> None:
        if isinstance(value, str):
            self.value = collapse_white_spaces(value)
        elif isinstance(value, bytes):
            self.value = collapse_white_spaces(value.decode('utf-8'))
        elif isinstance(value, self.__class__):
            self.value = value.value
        elif isinstance(value, UntypedAtomic):
            self.value = collapse_white_spaces(value.value)
        else:
            raise TypeError('the argument has an invalid type %r' % type(value))

        self.validate(self.value)

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

    def __str__(self) -> str:
        return self.value

    def __bool__(self) -> bool:
        return bool(self.value)  # For effective boolean value

    def __hash__(self) -> int:
        return hash(self.value)

    def __contains__(self, item: str) -> bool:
        return item in self.value

    def __eq__(self, other: object) -> bool:
        if isinstance(other, (AnyURI, UntypedAtomic)):
            return self.value == other.value
        elif isinstance(other, (bool, float, Decimal, Integer)):
            raise TypeError("cannot compare {} with xs:{}".format(type(other), self.name))
        return self.value == other

    def __ne__(self, other: object) -> bool:
        if isinstance(other, (AnyURI, UntypedAtomic)):
            return self.value != other.value
        elif isinstance(other, (bool, float, Decimal, Integer)):
            raise TypeError("cannot compare {} with xs:{}".format(type(other), self.name))
        return self.value != other

    def __lt__(self, other: Union[str, 'AnyURI', UntypedAtomic]) -> bool:
        if isinstance(other, (AnyURI, UntypedAtomic)):
            return self.value < other.value
        return self.value < other

    def __le__(self, other: Union[str, 'AnyURI', UntypedAtomic]) -> bool:
        if isinstance(other, (AnyURI, UntypedAtomic)):
            return self.value <= other.value
        return self.value <= other

    def __gt__(self, other: Union[str, 'AnyURI', UntypedAtomic]) -> bool:
        if isinstance(other, (AnyURI, UntypedAtomic)):
            return self.value > other.value
        return self.value > other

    def __ge__(self, other: Union[str, 'AnyURI', UntypedAtomic]) -> bool:
        if isinstance(other, (AnyURI, UntypedAtomic)):
            return self.value >= other.value
        return self.value >= other

    @classmethod
    def validate(cls, value: object) -> None:
        if isinstance(value, cls):
            return
        elif isinstance(value, bytes):
            value = value.decode()
        elif not isinstance(value, str):
            raise cls.invalid_type(value)

        try:
            url_parts = urlparse(value)
            _ = url_parts.port  # check invalid port!
        except ValueError as err:
            msg = 'invalid value {!r} for xs:{} ({})'
            raise ValueError(msg.format(value, cls.name, str(err))) from None
        else:
            if url_parts.path.startswith(':'):
                raise cls.invalid_value(value)
            elif value.count('#') > 1:
                msg = 'invalid value {!r} for xs:{} (too many # characters)'
                raise ValueError(msg.format(value, cls.name))
            elif WRONG_ESCAPE_PATTERN.search(value) is not None:
                msg = 'invalid value {!r} for xs:{} (wrong escaping)'
                raise ValueError(msg.format(value, cls.name))
elementpath/xpath_nodes.py
class TypedElement(XPathNode):
    """
    A class for processing typed element nodes.

    :param elem: the linked element. Can be an Element, or an XSD element \
    when XPath is applied on a schema.
    :param xsd_type: the reference XSD type.
    :param value: the decoded value. Can be `None` for empty or element-only elements."
    """
    def __init__(self, elem: ElementProtocol, xsd_type: Any, value: Any) -> None:
        self.elem = elem
        self.xsd_type = xsd_type
        self.value = value

    @property
    def kind(self) -> str:
        return 'element'

    @property
    def name(self) -> str:
        return self.elem.tag

    @property
    def tag(self) -> str:
        return self.elem.tag

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

    def __eq__(self, other: Any) -> bool:
        return isinstance(other, self.__class__) and \
            self.elem is other.elem and \
            self.value == other.value

    def __hash__(self) -> int:
        return hash((self.elem, self.value))
elementpath/datatypes/datetime.py
class YearMonthDuration(Duration):

    name = 'yearMonthDuration'

    def __init__(self, months: int = 0) -> None:
        super(YearMonthDuration, self).__init__(months, 0)

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

    def __str__(self) -> str:
        m = abs(self.months)
        years, months = m // 12, m % 12

        if not years:
            return '-P%dM' % months if self.months < 0 else 'P%dM' % months
        elif not months:
            return '-P%dY' % years if self.months < 0 else 'P%dY' % years
        elif self.months < 0:
            return '-P%dY%dM' % (years, months)
        else:
            return 'P%dY%dM' % (years, months)

    def __add__(self, other: object) \
            -> Union['YearMonthDuration', 'DayTimeDuration', 'OrderedDateTime']:
        if isinstance(other, self.__class__):
            return YearMonthDuration(months=self.months + other.months)
        elif isinstance(other, (DateTime10, Date10)):
            return other + self
        raise TypeError("cannot add %r to %r" % (type(other), type(self)))

    def __sub__(self, other: object) -> 'YearMonthDuration':
        if not isinstance(other, self.__class__):
            raise TypeError("cannot subtract %r from %r" % (type(other), type(self)))
        return YearMonthDuration(months=self.months - other.months)

    def __mul__(self, other: object) -> 'YearMonthDuration':
        if not isinstance(other, (float, int, Decimal)):
            raise TypeError("cannot multiply a %r by %r" % (type(self), type(other)))
        return YearMonthDuration(months=int(round_number(self.months * other)))

    def __truediv__(self, other: object) -> Union[float, 'YearMonthDuration']:
        if isinstance(other, self.__class__):
            return self.months / other.months
        elif isinstance(other, (float, int, Decimal)):
            return YearMonthDuration(months=int(round_number(self.months / other)))
        else:
            raise TypeError("cannot divide a %r by %r" % (type(self), type(other)))
elementpath/xpath_nodes.py
def is_element_node(obj: Any) -> bool:
    return isinstance(obj, TypedElement) or \
        hasattr(obj, 'tag') and not callable(obj.tag) and \
        hasattr(obj, 'attrib') and hasattr(obj, 'text')
elementpath/datatypes/proxies.py
class NumericProxy(metaclass=NumericTypeMeta):
    """Proxy for xs:numeric related types. Builds xs:float instances."""

    def __new__(cls, *args: FloatArgType, **kwargs: FloatArgType) -> float:  # type: ignore[misc]
        return float(*args, **kwargs)
elementpath/xpath_nodes.py
def is_xpath_node(obj: Any) -> bool:
    return isinstance(obj, XPathNode) or \
        hasattr(obj, 'tag') and hasattr(obj, 'attrib') and hasattr(obj, 'text') or \
        hasattr(obj, 'local_name') and hasattr(obj, 'type') and hasattr(obj, 'name') or \
        hasattr(obj, 'getroot') and hasattr(obj, 'parse') and hasattr(obj, 'iter')
elementpath/xpath_nodes.py
class XPathNode:

    name: Any = None
    value: Any = None
    parent: Optional[ElementNode] = None

    @property
    def kind(self) -> str:
        raise NotImplementedError()
elementpath/datatypes/datetime.py
class AbstractDateTime(metaclass=AtomicTypeMeta):
    """
    A class for representing XSD date/time objects. It uses and internal datetime.datetime
    attribute and an integer attribute for processing BCE years or for years after 9999 CE.
    """
    xsd_version = '1.0'
    pattern = re.compile(r'^$')
    _utc_timezone = Timezone(datetime.timedelta(0))
    _year = None

    def __init__(self, year: int = 2000, month: int = 1, day: int = 1, hour: int = 0,
                 minute: int = 0, second: int = 0, microsecond: int = 0,
                 tzinfo: Optional[datetime.tzinfo] = None) -> None:

        if hour == 24 and minute == second == microsecond == 0:
            delta = datetime.timedelta(days=1)
            hour = 0
        else:
            delta = datetime.timedelta(0)

        if 1 <= year <= 9999:
            self._dt = datetime.datetime(year, month, day, hour, minute,
                                         second, microsecond, tzinfo)
        elif year == 0:
            raise ValueError('0 is an illegal value for year')
        elif not isinstance(year, int):
            raise TypeError("invalid type %r for year" % type(year))
        elif abs(year) > 2 ** 31:
            raise OverflowError("year overflow")
        else:
            self._year = year
            if isleap(year + bool(self.xsd_version != '1.0')):
                self._dt = datetime.datetime(4, month, day, hour, minute,
                                             second, microsecond, tzinfo)
            else:
                self._dt = datetime.datetime(6, month, day, hour, minute,
                                             second, microsecond, tzinfo)

        if delta:
            self._dt += delta

    def __repr__(self) -> str:
        fields = self.pattern.groupindex.keys()
        arg_string = ', '.join(
            str(getattr(self, k))
            for k in ['year', 'month', 'day', 'hour', 'minute'] if k in fields
        )
        if 'second' in fields:
            if self.microsecond:
                arg_string += ', %d.%06d' % (self.second, self.microsecond)
            else:
                arg_string += ', %d' % self.second

        if self.tzinfo is not None:
            arg_string += ', tzinfo=%r' % self.tzinfo
        return '%s(%s)' % (self.__class__.__name__, arg_string)

    @abstractmethod
    def __str__(self) -> str:
        raise NotImplementedError()

    @property
    def year(self) -> int:
        return self._year or self._dt.year

    @property
    def bce(self) -> bool:
        return self._year is not None and self._year < 0

    @property
    def iso_year(self) -> str:
        """The ISO string representation of the year field."""
        year = self.year
        if -9999 <= year < -1:
            return '{:05}'.format(year if self.xsd_version == '1.0' else year + 1)
        elif year == -1:
            return '-0001' if self.xsd_version == '1.0' else '0000'
        elif 0 <= year <= 9999:
            return '{:04}'.format(year)
        else:
            return str(year)

    @property
    def month(self) -> int:
        return self._dt.month

    @property
    def day(self) -> int:
        return self._dt.day

    @property
    def hour(self) -> int:
        return self._dt.hour

    @property
    def minute(self) -> int:
        return self._dt.minute

    @property
    def second(self) -> int:
        return self._dt.second

    @property
    def microsecond(self) -> int:
        return self._dt.microsecond

    @property
    def tzinfo(self) -> Optional[Timezone]:
        return cast(Timezone, self._dt.tzinfo)

    @tzinfo.setter
    def tzinfo(self, tz: Timezone) -> None:
        self._dt = self._dt.replace(tzinfo=tz)

    def tzname(self) -> Optional[str]:
        return self._dt.tzname()

    def astimezone(self, tz: Optional[datetime.tzinfo] = None) -> datetime.datetime:
        return self._dt.astimezone(tz)

    def isocalendar(self) -> Tuple[int, int, int]:
        return self._dt.isocalendar()

    @classmethod
    def fromstring(cls, datetime_string: str, tzinfo: Optional[Timezone] = None) \
            -> 'AbstractDateTime':
        """
        Creates an XSD date/time instance from a string formatted value.

        :param datetime_string: a string containing an XSD formatted date/time specification.
        :param tzinfo: optional implicit timezone information, must be a `Timezone` instance.
        :return: an AbstractDateTime concrete subclass instance.
        """
        if not isinstance(datetime_string, str):
            msg = '1st argument has an invalid type {!r}'
            raise TypeError(msg.format(type(datetime_string)))
        elif tzinfo and not isinstance(tzinfo, Timezone):
            msg = '2nd argument has an invalid type {!r}'
            raise TypeError(msg.format(type(tzinfo)))

        match = cls.pattern.match(datetime_string.strip())
        if match is None:
            msg = 'Invalid datetime string {!r} for {!r}'
            raise ValueError(msg.format(datetime_string, cls))

        match_dict = match.groupdict()
        kwargs: Dict[str, int] = {
            k: int(v) for k, v in match_dict.items() if k != 'tzinfo' and v is not None
        }

        if match_dict['tzinfo'] is not None:
            tzinfo = Timezone.fromstring(match_dict['tzinfo'])

        if 'microsecond' in kwargs:
            microseconds = match_dict['microsecond']
            if len(microseconds) != 6:
                microseconds += '0' * (6 - len(microseconds))
                kwargs['microsecond'] = int(microseconds[:6])

        if 'year' in kwargs:
            year_digits = match_dict['year'].lstrip('-')
            if year_digits.startswith('0') and len(year_digits) > 4:
                msg = "Invalid datetime string {!r} for {!r} (when year " \
                      "exceeds 4 digits leading zeroes are not allowed)"
                raise ValueError(msg.format(datetime_string, cls))

            if cls.xsd_version == '1.0':
                if kwargs['year'] == 0:
                    raise ValueError("year '0000' is an illegal value for XSD 1.0")
            elif kwargs['year'] <= 0:
                kwargs['year'] -= 1

        return cls(tzinfo=tzinfo, **kwargs)

    @classmethod
    def fromdatetime(cls, dt: Union[datetime.datetime, datetime.date, datetime.time],
                     year: Optional[int] = None) -> 'AbstractDateTime':
        """
        Creates an XSD date/time instance from a datetime.datetime/date/time instance.

        :param dt: the datetime, date or time instance that stores the XSD Date/Time value.
        :param year: if an year is provided the created instance refers to it and the \
        possibly present *dt.year* part is ignored.
        :return: an AbstractDateTime concrete subclass instance.
        """
        if not isinstance(dt, (datetime.datetime, datetime.date, datetime.time)):
            raise TypeError('1st argument has an invalid type %r' % type(dt))
        elif year is not None and not isinstance(year, int):
            raise TypeError('2nd argument has an invalid type %r' % type(year))

        kwargs = {k: getattr(dt, k) for k in cls.pattern.groupindex.keys() if hasattr(dt, k)}
        if year is not None:
            kwargs['year'] = year
        return cls(**kwargs)

    # Python can't compares offset-naive and offset-aware datetimes
    def _get_operands(self, other: object) -> Tuple[datetime.datetime, datetime.datetime]:
        if isinstance(other, (self.__class__, datetime.datetime)) or \
                isinstance(self, other.__class__):
            dt: datetime.datetime = getattr(other, '_dt', cast(datetime.datetime, other))

            if self._dt.tzinfo is dt.tzinfo:
                return self._dt, dt
            elif self.tzinfo is None:
                return self._dt.replace(tzinfo=self._utc_timezone), dt
            elif dt.tzinfo is None:
                return self._dt, dt.replace(tzinfo=self._utc_timezone)
            else:
                return self._dt, dt
        else:
            raise TypeError("wrong type %r for operand %r" % (type(other), other))

    def __hash__(self) -> int:
        return hash((self._dt, self._year))

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, (AbstractDateTime, datetime.datetime)):
            return False
        try:
            return operator.eq(*self._get_operands(other)) and self.year == other.year
        except TypeError:
            return False

    def __ne__(self, other: object) -> bool:
        if not isinstance(other, (AbstractDateTime, datetime.datetime)):
            return True
        try:
            return operator.ne(*self._get_operands(other)) or self.year != other.year
        except TypeError:
            return True
elementpath/exceptions.py
class ElementPathTypeError(ElementPathError, TypeError):
    pass
elementpath/datatypes/datetime.py
class Duration(AnyAtomicType):
    """
    Base class for the XSD duration types.

    :param months: an integer value that represents years and months.
    :param seconds: a decimal or an integer instance that represents \
    days, hours, minutes, seconds and fractions of seconds.
    """
    name = 'duration'
    pattern = re.compile(
        r'^(-)?P(?=[0-9]|T)(?:([0-9]+)Y)?(?:([0-9]+)M)?(?:([0-9]+)D)?'
        r'(?:T(?=[0-9])(?:([0-9]+)H)?(?:([0-9]+)M)?(?:([0-9]+(?:\.[0-9]+)?)S)?)?$'
    )

    def __init__(self, months: int = 0, seconds: Union[Decimal, int] = 0) -> None:
        if seconds < 0 < months or months < 0 < seconds:
            raise ValueError('signs differ: (months=%d, seconds=%d)' % (months, seconds))
        elif abs(months) > 2 ** 31:
            raise OverflowError("months duration overflow")
        elif abs(seconds) > 2 ** 63:  # type: ignore[operator]
            raise OverflowError("seconds duration overflow")

        self.months = months
        self.seconds = Decimal(seconds).quantize(Decimal('1.000000'))

    def __repr__(self) -> str:
        return '{}(months={!r}, seconds={})'.format(
            self.__class__.__name__, self.months, normalized_seconds(self.seconds)
        )

    def __str__(self) -> str:
        m = abs(self.months)
        years, months = m // 12, m % 12
        s = self.seconds.copy_abs()
        days = int(s // 86400)
        hours = int(s // 3600 % 24)
        minutes = int(s // 60 % 60)
        seconds = s % 60

        value = '-P' if self.sign else 'P'
        if years or months or days:
            if years:
                value += '%dY' % years
            if months:
                value += '%dM' % months
            if days:
                value += '%dD' % days

        if hours or minutes or seconds:
            value += 'T'
            if hours:
                value += '%dH' % hours
            if minutes:
                value += '%dM' % minutes
            if seconds:
                value += '%sS' % normalized_seconds(seconds)

        elif value[-1] == 'P':
            value += 'T0S'
        return value

    @classmethod
    def fromstring(cls, text: str) -> 'Duration':
        """
        Creates a Duration instance from a formatted XSD duration string.

        :param text: an ISO 8601 representation without week fragment and an optional decimal part \
        only for seconds fragment.
        """
        if not isinstance(text, str):
            msg = 'argument has an invalid type {!r}'
            raise TypeError(msg.format(type(text)))

        match = cls.pattern.match(text.strip())
        if match is None:
            raise ValueError('%r is not an xs:duration value' % text)

        sign, y, mo, d, h, mi, s = match.groups()
        seconds = Decimal(s or 0)
        minutes = int(mi or 0) + int(seconds // 60)
        seconds = seconds % 60
        hours = int(h or 0) + minutes // 60
        minutes = minutes % 60
        days = int(d or 0) + hours // 24
        hours = hours % 24
        months = int(mo or 0) + 12 * int(y or 0)

        if sign is None:
            seconds = seconds + (days * 24 + hours) * 3600 + minutes * 60
        else:
            months = -months
            seconds = -seconds - (days * 24 + hours) * 3600 - minutes * 60

        if cls is DayTimeDuration:
            if months:
                raise ValueError('months must be 0 for %r' % cls.__name__)
            return cls(seconds=seconds)
        elif cls is YearMonthDuration:
            if seconds:
                raise ValueError('seconds must be 0 for %r' % cls.__name__)
            return cls(months=months)
        return cls(months=months, seconds=seconds)

    @property
    def sign(self) -> str:
        return '-' if self.months < 0 or self.seconds < 0 else ''

    def _compare_durations(self, other: object, op: Callable[[Any, Any], Any]) -> bool:
        """
        Ordering is defined through comparison of four datetime.datetime values.

        Ref: https://www.w3.org/TR/2012/REC-xmlschema11-2-20120405/#duration
        """
        if not isinstance(other, self.__class__):
            raise TypeError("wrong type %r for operand %r" % (type(other), other))

        m1, s1 = self.months, int(self.seconds)
        m2, s2 = other.months, int(other.seconds)
        ms1, ms2 = int((self.seconds - s1) * 1000000), int((other.seconds - s2) * 1000000)
        return all([
            op(datetime.timedelta(months2days(1696, 9, m1), s1, ms1),
               datetime.timedelta(months2days(1696, 9, m2), s2, ms2)),
            op(datetime.timedelta(months2days(1697, 2, m1), s1, ms1),
               datetime.timedelta(months2days(1697, 2, m2), s2, ms2)),
            op(datetime.timedelta(months2days(1903, 3, m1), s1, ms1),
               datetime.timedelta(months2days(1903, 3, m2), s2, ms2)),
            op(datetime.timedelta(months2days(1903, 7, m1), s1, ms1),
               datetime.timedelta(months2days(1903, 7, m2), s2, ms2)),
        ])

    def __hash__(self) -> int:
        return hash((self.months, self.seconds))

    def __eq__(self, other: object) -> bool:
        if isinstance(other, self.__class__):
            return self.months == other.months and self.seconds == other.seconds
        elif isinstance(other, UntypedAtomic):
            return self.__eq__(self.fromstring(other.value))
        else:
            return other == (self.months, self.seconds)

    def __ne__(self, other: object) -> bool:
        if isinstance(other, self.__class__):
            return self.months != other.months or self.seconds != other.seconds
        elif isinstance(other, UntypedAtomic):
            return self.__ne__(self.fromstring(other.value))
        else:
            return other != (self.months, self.seconds)

    def __lt__(self, other: object) -> bool:
        return self._compare_durations(other, operator.lt)

    def __le__(self, other: object) -> bool:
        return self == other or self._compare_durations(other, operator.le)

    def __gt__(self, other: object) -> bool:
        return self._compare_durations(other, operator.gt)

    def __ge__(self, other: object) -> bool:
        return self == other or self._compare_durations(other, operator.ge)
elementpath/exceptions.py
class ElementPathKeyError(ElementPathError, KeyError):
    pass
elementpath/xpath_context.py
class XPathSchemaContext(XPathContext):
    """
    The XPath dynamic context base class for schema bounded parsers. Use this class
    as dynamic context for schema instances in order to perform a schema-based type
    checking during the static analysis phase. Don't use this as dynamic context on
    XML instances.
    """
    iter_children_or_self: Callable[..., Iterator[Union[XsdElementProtocol, XMLSchemaProtocol]]]
    root: XMLSchemaProtocol
elementpath/datatypes/proxies.py
class ArithmeticProxy(metaclass=ArithmeticTypeMeta):
    """Proxy for arithmetic related types. Builds xs:float instances."""

    def __new__(cls, *args: FloatArgType, **kwargs: FloatArgType) -> float:  # type: ignore[misc]
        return float(*args, **kwargs)
elementpath/namespaces.py
XMLNS_NAMESPACE = "http://www.w3.org/2000/xmlns/"  # Used in DOM for xmlns declarations


Question: import math
import decimal
import operator
from copy import copy
from ..datatypes import AnyURI
from ..exceptions import ElementPathKeyError, ElementPathTypeError
from ..helpers import collapse_white_spaces
from ..datatypes import AbstractDateTime, Duration, DayTimeDuration, \
    YearMonthDuration, NumericProxy, ArithmeticProxy
from ..xpath_context import XPathSchemaContext
from ..namespaces import XMLNS_NAMESPACE, XSD_NAMESPACE
from ..schema_proxy import AbstractSchemaProxy
from ..xpath_nodes import XPathNode, TypedElement, AttributeNode, TypedAttribute, \
    is_xpath_node, is_schema_node, is_document_node, is_element_node
from .xpath1_parser import XPath1Parser
        self.value = '{%s}%s' % (self[0].value, self[1].value)
    return self


@method('{')
def evaluate_namespace_uri(self, context=None):
    if self[1].label.endswith('function'):
        return self[1].evaluate(context)
    return [x for x in self.select(context)]


@method('{')
def select_namespace_uri(self, context=None):
    if self[1].label.endswith('function'):
        yield self[1].evaluate(context)
        return
    elif context is None:
        raise self.missing_context()

    if isinstance(context, XPathSchemaContext):
        yield from self.select_xsd_nodes(context, self.value)

    elif self.xsd_types is None:
        for item in context.iter_children_or_self():
            if context.match_name(self.value):
                yield item
    else:
        # XSD typed selection
        for item in context.iter_children_or_self():
            if context.match_name(self.value):
                if isinstance(item, (TypedAttribute, TypedElement)):
                    yield item
                else:
                    context.item = self.get_typed_node(item)
                    yield context.item


###
# Variables
@method('$', bp=90)
def nud_variable_reference(self):
    self.parser.expected_name('(name)')
    self[:] = self.parser.expression(rbp=90),
    if ':' in self[0].value:
        raise self[0].wrong_syntax("variable reference requires a simple reference name")
    return self


@method('$')
def evaluate_variable_reference(self, context=None):
    if context is None:
        raise self.missing_context()

    try:
        return context.variables[self[0].value]
    except KeyError as err:
        raise self.missing_name('unknown variable %r' % str(err)) from None


###
# Nullary operators (use only the context)
@method(nullary('*'))
def select_wildcard(self, context=None):
    if self:
        # Product operator
        item = self.evaluate(context)
        if item is not None:
            if context is not None:
                context.item = item
            yield item
    elif context is None:
        raise self.missing_context()

    # Wildcard literal
    elif isinstance(context, XPathSchemaContext):
        for item in context.iter_children_or_self():
            if item is not None:
                self.add_xsd_type(item)
                yield item

    elif self.xsd_types is None:
        for item in context.iter_children_or_self():
            if item is None:
                pass  # '*' wildcard doesn't match document nodes
            elif context.axis == 'attribute':
                if isinstance(item, (AttributeNode, TypedAttribute)):
                    yield item
            elif is_element_node(item):
                yield item

    else:
        # XSD typed selection
        for item in context.iter_children_or_self():
            if context.item is not None and context.is_principal_node_kind():
                if isinstance(item, (TypedAttribute, TypedElement)):
                    yield item
                else:
                    context.item = self.get_typed_node(item)
                    yield context.item


@method(nullary('.'))
def select_self_shortcut(self, context=None):
    if context is None:
        raise self.missing_context()

    elif isinstance(context, XPathSchemaContext):
        for item in context.iter_self():
            if is_schema_node(item):
                self.add_xsd_type(item)
            elif item is context.root:
                # item is the schema
                for xsd_element in item:
                    self.add_xsd_type(xsd_element)
            yield item

    elif self.xsd_types is None:
        for item in context.iter_self():
            if item is not None:
                yield item

Answer:             elif is_document_node(context.root):