elementpath/helpers.py
NUMERIC_INF_OR_NAN = frozenset(('INF', '-INF', 'NaN'))
elementpath/helpers.py
BOOLEAN_VALUES = frozenset(('true', 'false', '1', '0'))
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/datatypes/untyped.py
class UntypedAtomic(metaclass=AtomicTypeMeta):
    """
    Class for xs:untypedAtomic data. Provides special methods for comparing
    and converting to basic data types.

    :param value: the untyped value, usually a string.
    """
    name = 'untypedAtomic'
    value: str

    @classmethod
    def validate(cls, value: object) -> None:
        if not isinstance(value, cls):
            raise cls.invalid_type(value)

    def __init__(self, value: Union[str, bytes, bool, float, Decimal,
                                    'UntypedAtomic', AnyAtomicType]) -> None:
        if isinstance(value, str):
            self.value = value
        elif isinstance(value, bytes):
            self.value = value.decode('utf-8')
        elif isinstance(value, bool):
            self.value = 'true' if value else 'false'
        elif isinstance(value, float):
            self.value = str(value).rstrip('0').rstrip('.')
        elif isinstance(value, Decimal):
            self.value = str(value.normalize())
        elif isinstance(value, UntypedAtomic):
            self.value = value.value
        elif isinstance(value, AnyAtomicType):
            self.value = str(value)
        else:
            raise TypeError("{!r} is not an atomic value".format(value))

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

    def _get_operands(self, other: Any, force_float: bool = True) -> Tuple[Any, Any]:
        """
        Returns a couple of operands, applying a cast to the instance value based on
        the type of the *other* argument.

        :param other: The other operand, that determines the cast for the untyped instance.
        :param force_float: Force a conversion to float if *other* is an UntypedAtomic instance.
        :return: A couple of values.
        """
        if isinstance(other, UntypedAtomic):
            if force_float:
                return float(self.value), float(other.value)
            return self.value, other.value
        elif isinstance(other, bool):
            # Cast to xs:boolean
            value = self.value.strip()
            if value not in BOOLEAN_VALUES:
                raise ValueError("{!r} cannot be cast to xs:boolean".format(self.value))
            return value in ('1', 'true'), other
        elif isinstance(other, int):
            return float(self.value), other
        elif other is None or isinstance(other, (str, list)):
            return self.value, other

        try:
            return type(other).fromstring(self.value), other
        except AttributeError:
            return type(other)(self.value), other

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

    def __eq__(self, other: Any) -> Any:
        return operator.eq(*self._get_operands(other, force_float=False))

    def __ne__(self, other: Any) -> Any:
        return not operator.eq(*self._get_operands(other, force_float=False))

    def __lt__(self, other: Any) -> Any:
        return operator.lt(*self._get_operands(other))

    def __le__(self, other: Any) -> Any:
        return operator.le(*self._get_operands(other))

    def __gt__(self, other: Any) -> Any:
        return operator.gt(*self._get_operands(other))

    def __ge__(self, other: Any) -> Any:
        return operator.ge(*self._get_operands(other))

    def __add__(self, other: Any) -> Any:
        return operator.add(*self._get_operands(other))
    __radd__ = __add__

    def __sub__(self, other: Any) -> Any:
        return operator.sub(*self._get_operands(other))

    def __rsub__(self, other: Any) -> Any:
        return operator.sub(*reversed(self._get_operands(other)))

    def __mul__(self, other: Any) -> Any:
        return operator.mul(*self._get_operands(other))
    __rmul__ = __mul__

    def __truediv__(self, other: Any) -> Any:
        return operator.truediv(*self._get_operands(other))

    def __rtruediv__(self, other: Any) -> Any:
        return operator.truediv(*reversed(self._get_operands(other)))

    def __int__(self) -> int:
        return int(self.value)

    def __float__(self) -> float:
        return float(self.value)

    def __bool__(self) -> bool:
        return bool(self.value)  # For effective boolean value, not for cast to xs:boolean.

    def __abs__(self) -> Decimal:
        return abs(Decimal(self.value))

    def __mod__(self, other: Any) -> Any:
        return operator.mod(*self._get_operands(other))

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

    def __bytes__(self) -> bytes:
        return bytes(self.value, encoding='utf-8')
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/datatypes/atomic_types.py
class AtomicTypeMeta(ABCMeta):
    """
    Metaclass for creating XSD atomic types. The created classes
    are decorated with missing attributes and methods. When a name
    attribute is provided the class is registered into a global map
    of XSD atomic types and also the expanded name is added.
    """
    xsd_version: str
    pattern: Pattern[str]
    name: Optional[str] = None

    def __new__(mcs, class_name: str, bases: Tuple[Type[Any], ...], dict_: Dict[str, Any]) \
            -> 'AtomicTypeMeta':
        try:
            name = dict_['name']
        except KeyError:
            name = dict_['name'] = None  # do not inherit name

        if name is not None and not isinstance(name, str):
            raise TypeError("attribute 'name' must be a string or None")

        dict_['is_valid'] = classmethod(mcs.is_valid)
        dict_['invalid_type'] = classmethod(mcs.invalid_type)
        dict_['invalid_value'] = classmethod(mcs.invalid_value)
        cls = super(AtomicTypeMeta, mcs).__new__(mcs, class_name, bases, dict_)

        # Add missing attributes and methods
        if not hasattr(cls, 'xsd_version'):
            cls.xsd_version = '1.0'
        if not hasattr(cls, 'pattern'):
            cls.pattern = re.compile(r'^$')

        # Register class with a name
        if name:
            expanded_name = '{%s}%s' % (XSD_NAMESPACE, name)
            if cls.xsd_version == '1.0':
                xsd10_atomic_types[name] = xsd10_atomic_types[expanded_name] = cls
            else:
                xsd11_atomic_types[name] = xsd11_atomic_types[expanded_name] = cls

        return cls

    def validate(cls, value: object) -> None:
        if isinstance(value, cls):
            return
        elif isinstance(value, str):
            if cls.pattern.match(value) is None:
                raise cls.invalid_value(value)
        else:
            raise cls.invalid_type(value)

    def is_valid(cls, value: object) -> bool:
        try:
            cls.validate(value)
        except (TypeError, ValueError):
            return False
        else:
            return True

    def invalid_type(cls, value: object) -> TypeError:
        if cls.name:
            return TypeError('invalid type {!r} for xs:{}'.format(type(value), cls.name))
        return TypeError('invalid type {!r} for {!r}'.format(type(value), cls))

    def invalid_value(cls, value: object) -> ValueError:
        if cls.name:
            return ValueError('invalid value {!r} for xs:{}'.format(value, cls.name))
        return ValueError('invalid value {!r} for {!r}'.format(value, cls))
elementpath/helpers.py
INVALID_NUMERIC = frozenset(
    ('inf', '+inf', '-inf', 'nan', 'infinity', '+infinity', '-infinity')
)
elementpath/datatypes/numeric.py
class Integer(int, metaclass=AtomicTypeMeta):
    """A wrapper for emulating xs:integer and limited integer types."""
    name = 'integer'
    pattern = re.compile(r'^[\-+]?[0-9]+$')
    lower_bound: Optional[int] = None
    higher_bound: Optional[int] = None

    def __init__(self, value: Union[str, SupportsInt]) -> None:
        if self.lower_bound is not None and self < self.lower_bound:
            raise ValueError("value {} is too low for {!r}".format(value, self.__class__))
        elif self.higher_bound is not None and self >= self.higher_bound:
            raise ValueError("value {} is too high for {!r}".format(value, self.__class__))
        super(Integer, self).__init__()

    @classmethod
    def __subclasshook__(cls, subclass: Type[Any]) -> bool:
        if cls is Integer:
            return issubclass(subclass, int) and not issubclass(subclass, bool)
        return NotImplemented

    @classmethod
    def validate(cls, value: object) -> None:
        if isinstance(value, cls):
            return
        elif isinstance(value, str):
            if cls.pattern.match(value) is None:
                raise cls.invalid_value(value)
        else:
            raise cls.invalid_type(value)
elementpath/datatypes/numeric.py
class Float10(float, AnyAtomicType):
    name = 'float'
    xsd_version = '1.0'
    pattern = re.compile(
        r'^(?:[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[Ee][+-]?[0-9]+)? |[+-]?INF|NaN)$'
    )

    def __new__(cls, value: Union[str, SupportsFloat]) -> 'Float10':
        if isinstance(value, str):
            value = collapse_white_spaces(value)
            if value in NUMERIC_INF_OR_NAN or cls.xsd_version != '1.0' and value == '+INF':
                pass
            elif value.lower() in INVALID_NUMERIC:
                raise ValueError('invalid value {!r} for xs:{}'.format(value, cls.name))

        _value = super().__new__(cls, value)
        if _value > 3.4028235E38:
            return super().__new__(cls, 'INF')
        elif _value < -3.4028235E38:
            return super().__new__(cls, '-INF')
        elif -1e-37 < _value < 1e-37:
            return super().__new__(cls, -0.0 if str(_value).startswith('-') else 0.0)
        return _value

    def __hash__(self) -> int:
        return super(Float10, self).__hash__()

    def __eq__(self, other: object) -> bool:
        if isinstance(other, self.__class__):
            if super(Float10, self).__eq__(other):
                return True
            return math.isclose(self, other, rel_tol=1e-7, abs_tol=0.0)
        return super(Float10, self).__eq__(other)

    def __ne__(self, other: object) -> bool:
        if isinstance(other, self.__class__):
            if super(Float10, self).__eq__(other):
                return False
            return not math.isclose(self, other, rel_tol=1e-7, abs_tol=0.0)
        return super(Float10, self).__ne__(other)

    def __add__(self, other: object) -> Union[float, 'Float10', 'Float']:
        if isinstance(other, (self.__class__, int)):
            return self.__class__(super(Float10, self).__add__(other))
        elif isinstance(other, float):
            return super(Float10, self).__add__(other)
        return NotImplemented

    def __radd__(self, other: object) -> Union[float, 'Float10', 'Float']:
        if isinstance(other, (self.__class__, int)):
            return self.__class__(super(Float10, self).__radd__(other))
        elif isinstance(other, float):
            return super(Float10, self).__radd__(other)
        return NotImplemented

    def __sub__(self, other: object) -> Union[float, 'Float10', 'Float']:
        if isinstance(other, (self.__class__, int)):
            return self.__class__(super(Float10, self).__sub__(other))
        elif isinstance(other, float):
            return super(Float10, self).__sub__(other)
        return NotImplemented

    def __rsub__(self, other: object) -> Union[float, 'Float10', 'Float']:
        if isinstance(other, (self.__class__, int)):
            return self.__class__(super(Float10, self).__rsub__(other))
        elif isinstance(other, float):
            return super(Float10, self).__rsub__(other)
        return NotImplemented

    def __mul__(self, other: object) -> Union[float, 'Float10', 'Float']:
        if isinstance(other, (self.__class__, int)):
            return self.__class__(super(Float10, self).__mul__(other))
        elif isinstance(other, float):
            return super(Float10, self).__mul__(other)
        return NotImplemented

    def __rmul__(self, other: object) -> Union[float, 'Float10', 'Float']:
        if isinstance(other, (self.__class__, int)):
            return self.__class__(super(Float10, self).__rmul__(other))
        elif isinstance(other, float):
            return super(Float10, self).__rmul__(other)
        return NotImplemented

    def __truediv__(self, other: object) -> Union[float, 'Float10', 'Float']:
        if isinstance(other, (self.__class__, int)):
            return self.__class__(super(Float10, self).__truediv__(other))
        elif isinstance(other, float):
            return super(Float10, self).__truediv__(other)
        return NotImplemented

    def __rtruediv__(self, other: object) -> Union[float, 'Float10', 'Float']:
        if isinstance(other, (self.__class__, int)):
            return self.__class__(super(Float10, self).__rtruediv__(other))
        elif isinstance(other, float):
            return super(Float10, self).__rtruediv__(other)
        return NotImplemented

    def __mod__(self, other: object) -> Union[float, 'Float10', 'Float']:
        if isinstance(other, (self.__class__, int)):
            return self.__class__(super(Float10, self).__mod__(other))
        elif isinstance(other, float):
            return super(Float10, self).__mod__(other)
        return NotImplemented

    def __rmod__(self, other: object) -> Union[float, 'Float10', 'Float']:
        if isinstance(other, (self.__class__, int)):
            return self.__class__(super(Float10, self).__rmod__(other))
        elif isinstance(other, float):
            return super(Float10, self).__rmod__(other)
        return NotImplemented

    def __abs__(self) -> Union['Float10', 'Float']:
        return self.__class__(super(Float10, self).__abs__())
elementpath/helpers.py
def collapse_white_spaces(s: str) -> str:
    return WHITESPACES_PATTERN.sub(' ', s).strip(' ')


Question: import re
import math
from decimal import Decimal
from typing import Any, Union, SupportsFloat
from ..helpers import BOOLEAN_VALUES, NUMERIC_INF_OR_NAN, INVALID_NUMERIC, \
    collapse_white_spaces
from .atomic_types import AtomicTypeMeta
from .untyped import UntypedAtomic
from .numeric import Float10, Integer
from .datetime import AbstractDateTime, Duration
                raise cls.invalid_value(value)
        else:
            raise cls.invalid_type(value)


class DecimalProxy(metaclass=AtomicTypeMeta):
    name = 'decimal'
    pattern = re.compile(r'^[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)$')

    def __new__(cls, value: Any) -> Decimal:  # type: ignore[misc]
        if isinstance(value, (str, UntypedAtomic)):
            value = collapse_white_spaces(str(value)).replace(' ', '')
            if cls.pattern.match(value) is None:
                raise cls.invalid_value(value)
        elif isinstance(value, (float, Float10, Decimal)):
            if math.isinf(value) or math.isnan(value):
                raise cls.invalid_value(value)
        try:
            return Decimal(value)
        except (ValueError, ArithmeticError):
            msg = 'invalid value {!r} for xs:{}'
            raise ArithmeticError(msg.format(value, cls.name)) from None

    @classmethod
    def __subclasshook__(cls, subclass: type) -> bool:
        return issubclass(subclass, (int, Decimal, Integer)) and not issubclass(subclass, bool)

    @classmethod
    def validate(cls, value: object) -> None:
        if isinstance(value, Decimal):
            if math.isnan(value) or math.isinf(value):
                raise cls.invalid_value(value)
        elif isinstance(value, (int, Integer)) and not isinstance(value, bool):
            return
        elif isinstance(value, str):
            if cls.pattern.match(value) is None:
                raise cls.invalid_value(value)
        else:
            raise cls.invalid_type(value)


class DoubleProxy10(metaclass=AtomicTypeMeta):
    name = 'double'
    xsd_version = '1.0'
    pattern = re.compile(
        r'^(?:[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[Ee][+-]?[0-9]+)?|[+-]?INF|NaN)$'
    )

    def __new__(cls, value: Union[SupportsFloat, str]) -> float:  # type: ignore[misc]
        if isinstance(value, str):
            value = collapse_white_spaces(value)
            if value in NUMERIC_INF_OR_NAN or cls.xsd_version != '1.0' and value == '+INF':
                pass
            elif value.lower() in INVALID_NUMERIC:
                raise ValueError('invalid value {!r} for xs:{}'.format(value, cls.name))
        return float(value)

    @classmethod
    def __subclasshook__(cls, subclass: type) -> bool:
        return issubclass(subclass, float) and not issubclass(subclass, Float10)

    @classmethod
    def validate(cls, value: object) -> None:
        if isinstance(value, float) and not isinstance(value, Float10):
            return
        elif isinstance(value, str):
            if cls.pattern.match(value) is None:
                raise cls.invalid_value(value)
        else:
            raise cls.invalid_type(value)


class DoubleProxy(DoubleProxy10):
    name = 'double'
    xsd_version = '1.1'


class StringProxy(metaclass=AtomicTypeMeta):
    name = 'string'

    def __new__(cls, *args: object, **kwargs: object) -> str:  # type: ignore[misc]
        return str(*args, **kwargs)

    @classmethod
    def __subclasshook__(cls, subclass: type) -> bool:
        return issubclass(subclass, str)

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


####
# Type proxies for multiple type-checking in XPath expressions
class NumericTypeMeta(type):
    """Metaclass for checking numeric classes and instances."""

    def __instancecheck__(cls, instance: object) -> bool:
        return isinstance(instance, (int, float, Decimal)) and not isinstance(instance, bool)

    def __subclasscheck__(cls, subclass: type) -> bool:
        if issubclass(subclass, bool):
            return False
        return issubclass(subclass, int) or issubclass(subclass, float) \
            or issubclass(subclass, Decimal)


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)


class ArithmeticTypeMeta(type):
    """Metaclass for checking numeric, datetime and duration classes/instances."""

    def __instancecheck__(cls, instance: object) -> bool:
        return isinstance(

Answer:             instance, (int, float, Decimal, AbstractDateTime, Duration, UntypedAtomic)