1359 lines
47 KiB
Python
1359 lines
47 KiB
Python
# -*- coding: utf-8 -*-
|
|
#
|
|
# Copyright (c), 2018-2019, SISSA (International School for Advanced Studies).
|
|
# All rights reserved.
|
|
# This file is distributed under the terms of the MIT License.
|
|
# See the file 'LICENSE' in the root directory of the present
|
|
# distribution, or http://opensource.org/licenses/MIT.
|
|
#
|
|
# @author Davide Brunato <brunato@sissa.it>
|
|
#
|
|
"""
|
|
XSD atomic datatypes for XPath. Includes a class for UntypedAtomic data and some
|
|
classes for XSD datetime and duration types.
|
|
"""
|
|
from __future__ import division, unicode_literals
|
|
|
|
from abc import ABCMeta, abstractmethod
|
|
import operator
|
|
import re
|
|
import decimal
|
|
import datetime
|
|
import base64
|
|
from collections import namedtuple
|
|
from calendar import isleap, leapdays
|
|
|
|
from .compat import PY3, string_base_type, add_metaclass
|
|
from .exceptions import ElementPathTypeError, ElementPathValueError
|
|
|
|
###
|
|
# Date/Time helpers
|
|
MONTH_DAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
|
MONTH_DAYS_LEAP = [0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
|
|
|
|
|
def adjust_day(year, month, day):
|
|
if month in {1, 3, 5, 7, 8, 10, 12}:
|
|
return day
|
|
elif month in {4, 6, 9, 11}:
|
|
return min(day, 30)
|
|
else:
|
|
return min(day, 29) if isleap(year) else min(day, 28)
|
|
|
|
|
|
def days_from_common_era(year):
|
|
"""
|
|
Returns the number of days from from 0001-01-01 to the provided year. For a
|
|
common era year the days are counted until the last day of December, for a
|
|
BCE year the days are counted down from the end to the 1st of January.
|
|
"""
|
|
if year > 0:
|
|
return year * 365 + year // 4 - year // 100 + year // 400
|
|
elif year >= -1:
|
|
return year * 366
|
|
else:
|
|
year = -year - 1
|
|
return -(366 + year * 365 + year // 4 - year // 100 + year // 400)
|
|
|
|
|
|
DAYS_IN_4Y = days_from_common_era(4)
|
|
DAYS_IN_100Y = days_from_common_era(100)
|
|
DAYS_IN_400Y = days_from_common_era(400)
|
|
|
|
|
|
def months2days(year, month, months_delta):
|
|
"""
|
|
Converts a delta of months to a delta of days, counting from the 1st day of the month,
|
|
relative to the year and the month passed as arguments.
|
|
|
|
:param year: the reference start year, a negative or zero value means a BCE year \
|
|
(0 is 1 BCE, -1 is 2 BCE, -2 is 3 BCE, etc).
|
|
:param month: the starting month (1-12).
|
|
:param months_delta: the number of months, if negative count backwards.
|
|
"""
|
|
if not months_delta:
|
|
return 0
|
|
|
|
total_months = month - 1 + months_delta
|
|
target_year = year + total_months // 12
|
|
target_month = total_months % 12 + 1
|
|
|
|
if month <= 2:
|
|
y_days = 365 * (target_year - year) + leapdays(year, target_year)
|
|
else:
|
|
y_days = 365 * (target_year - year) + leapdays(year + 1, target_year + 1)
|
|
|
|
months_days = MONTH_DAYS_LEAP if isleap(target_year) else MONTH_DAYS
|
|
if target_month >= month:
|
|
m_days = sum(months_days[m] for m in range(month, target_month))
|
|
return y_days + m_days if y_days >= 0 else y_days + m_days
|
|
else:
|
|
m_days = sum(months_days[m] for m in range(target_month, month))
|
|
return y_days - m_days if y_days >= 0 else y_days - m_days
|
|
|
|
|
|
class Timezone(datetime.tzinfo):
|
|
"""
|
|
A tzinfo implementation for XSD timezone offsets. Offsets must be specified
|
|
between -14:00 and +14:00.
|
|
|
|
:param offset: a timedelta instance or an XSD timezone formatted string.
|
|
"""
|
|
_maxoffset = datetime.timedelta(hours=14, minutes=0)
|
|
_minoffset = -_maxoffset
|
|
|
|
def __init__(self, offset):
|
|
super(Timezone, self).__init__()
|
|
if not isinstance(offset, datetime.timedelta):
|
|
raise ElementPathTypeError("offset must be a datetime.timedelta or an XSD timezone formatted string")
|
|
if offset < self._minoffset or offset > self._maxoffset:
|
|
raise ElementPathValueError("offset must be between -14:00 and +14:00")
|
|
self.offset = offset
|
|
|
|
@classmethod
|
|
def fromstring(cls, text):
|
|
if text == 'Z':
|
|
return cls(datetime.timedelta(0))
|
|
elif isinstance(text, string_base_type):
|
|
try:
|
|
hours, minutes = text.split(':')
|
|
hours = int(hours)
|
|
minutes = int(minutes) if hours >= 0 else -int(minutes)
|
|
return cls(datetime.timedelta(hours=hours, minutes=minutes))
|
|
except ValueError:
|
|
raise ElementPathValueError("%r: not an XSD timezone formatted string" % text)
|
|
|
|
@classmethod
|
|
def fromduration(cls, duration):
|
|
return cls(datetime.timedelta(seconds=int(duration.seconds)))
|
|
|
|
def __getinitargs__(self):
|
|
return self.offset,
|
|
|
|
def __hash__(self):
|
|
return hash(self.offset)
|
|
|
|
def __eq__(self, other):
|
|
return isinstance(other, Timezone) and self.offset == other.offset
|
|
|
|
def __ne__(self, other):
|
|
return not isinstance(other, Timezone) or self.offset != other.offset
|
|
|
|
def __repr__(self):
|
|
return "%s(%r)" % (self.__class__.__name__, self.offset)
|
|
|
|
def __str__(self):
|
|
return self.tzname(None)
|
|
|
|
def utcoffset(self, dt):
|
|
if not isinstance(dt, datetime.datetime) and dt is not None:
|
|
raise ElementPathTypeError("utcoffset() argument must be a datetime.datetime instance or None")
|
|
return self.offset
|
|
|
|
def tzname(self, dt):
|
|
if not isinstance(dt, datetime.datetime) and dt is not None:
|
|
raise ElementPathTypeError("tzname() argument must be a datetime.datetime instance or None")
|
|
|
|
if not self.offset:
|
|
return 'Z'
|
|
elif self.offset < datetime.timedelta(0):
|
|
sign, offset = '-', -self.offset
|
|
else:
|
|
sign, offset = '+', self.offset
|
|
|
|
hours, minutes = offset.seconds // 3600, offset.seconds // 60 % 60
|
|
return '{}{:02d}:{:02d}'.format(sign, hours, minutes)
|
|
|
|
def dst(self, dt):
|
|
if not isinstance(dt, datetime.datetime) and dt is not None:
|
|
raise ElementPathTypeError("dst() argument must be a datetime.datetime instance or None")
|
|
|
|
def fromutc(self, dt):
|
|
if isinstance(dt, datetime.datetime):
|
|
return dt + self.offset
|
|
elif dt is not None:
|
|
raise TypeError("fromutc() argument must be a datetime.datetime instance or None")
|
|
|
|
|
|
@add_metaclass(ABCMeta)
|
|
class AbstractDateTime(object):
|
|
"""
|
|
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.
|
|
"""
|
|
version = '1.0'
|
|
_pattern = re.compile(r'^$')
|
|
_utc_timezone = Timezone(datetime.timedelta(0))
|
|
|
|
def __init__(self, year=2000, month=1, day=1, hour=0, minute=0, second=0, microsecond=0, tzinfo=None):
|
|
if hour == 24 and minute == second == 0:
|
|
hour = 0
|
|
|
|
if 1 <= year <= 9999:
|
|
self._year = None
|
|
self._dt = datetime.datetime(year, month, day, hour, minute, second, microsecond, tzinfo)
|
|
elif year == 0:
|
|
raise ElementPathValueError('0 is an illegal value for year')
|
|
elif not isinstance(year, int):
|
|
raise ElementPathTypeError("wrong type %r for year" % type(year))
|
|
else:
|
|
self._year = year
|
|
if isleap(year):
|
|
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)
|
|
|
|
def __repr__(self):
|
|
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):
|
|
pass
|
|
|
|
def __unicode__(self):
|
|
return str(self)
|
|
|
|
@property
|
|
def year(self):
|
|
return self._year or self._dt.year
|
|
|
|
@property
|
|
def bce(self):
|
|
return self._year is not None and self._year < 0
|
|
|
|
@property
|
|
def iso_year(self):
|
|
"""The ISO string representation of the year field."""
|
|
year = self.year
|
|
if -9999 <= year < -1:
|
|
return '{:05}'.format(year if self.version == '1.0' else year + 1)
|
|
elif year == -1:
|
|
return '{:04}'.format(year if self.version == '1.0' else year + 1)
|
|
elif 0 <= year <= 9999:
|
|
return '{:04}'.format(year)
|
|
else:
|
|
return str(year)
|
|
|
|
@property
|
|
def month(self):
|
|
return self._dt.month
|
|
|
|
@property
|
|
def day(self):
|
|
return self._dt.day
|
|
|
|
@property
|
|
def hour(self):
|
|
return self._dt.hour
|
|
|
|
@property
|
|
def minute(self):
|
|
return self._dt.minute
|
|
|
|
@property
|
|
def second(self):
|
|
return self._dt.second
|
|
|
|
@property
|
|
def microsecond(self):
|
|
return self._dt.microsecond
|
|
|
|
@property
|
|
def tzinfo(self):
|
|
return self._dt.tzinfo
|
|
|
|
@tzinfo.setter
|
|
def tzinfo(self, tz):
|
|
self._dt = self._dt.replace(tzinfo=tz)
|
|
|
|
@classmethod
|
|
def fromstring(cls, datetime_string, tzinfo=None):
|
|
"""
|
|
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, string_base_type):
|
|
raise ElementPathTypeError('1st argument has an invalid type %r' % type(datetime_string))
|
|
elif tzinfo and not isinstance(tzinfo, Timezone):
|
|
raise ElementPathTypeError('2nd argument has an invalid type %r' % type(tzinfo))
|
|
|
|
match = cls._pattern.match(datetime_string)
|
|
if match is None:
|
|
raise ElementPathValueError('Invalid datetime string %r for %r' % (datetime_string, cls))
|
|
|
|
kwargs = {k: int(v) if k != 'tzinfo' else Timezone.fromstring(v)
|
|
for k, v in match.groupdict().items() if v is not None}
|
|
|
|
if 'tzinfo' not in kwargs and tzinfo is not None:
|
|
kwargs['tzinfo'] = tzinfo
|
|
if 'microsecond' in kwargs:
|
|
pow10 = 6 - len(match.groupdict()['microsecond'])
|
|
kwargs['microsecond'] = 0 if pow10 < 0 else kwargs['microsecond'] * 10**pow10
|
|
|
|
year = kwargs.get('year')
|
|
if year is not None and year <= 0 and cls.version != '1.0':
|
|
kwargs['year'] -= 1
|
|
return cls(**kwargs)
|
|
|
|
@classmethod
|
|
def fromdatetime(cls, dt, year=None):
|
|
"""
|
|
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 ElementPathTypeError('1st argument has an invalid type %r' % type(dt))
|
|
elif year is not None and not isinstance(year, int):
|
|
raise ElementPathTypeError('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):
|
|
if isinstance(other, (self.__class__, datetime.datetime)) or isinstance(self, other.__class__):
|
|
dt = getattr(other, '_dt', 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 ElementPathTypeError("wrong type %r for operand %r." % (type(other), other))
|
|
|
|
def __hash__(self):
|
|
return hash((self._dt, self._year))
|
|
|
|
def __eq__(self, other):
|
|
try:
|
|
return operator.eq(*self._get_operands(other)) and self.year == other.year
|
|
except ElementPathTypeError:
|
|
return False
|
|
|
|
def __ne__(self, other):
|
|
try:
|
|
return operator.ne(*self._get_operands(other)) or self.year != other.year
|
|
except ElementPathTypeError:
|
|
return True
|
|
|
|
|
|
class OrderedDateTime(AbstractDateTime):
|
|
@abstractmethod
|
|
def __str__(self):
|
|
pass
|
|
|
|
@classmethod
|
|
def fromdelta(cls, delta, adjust_timezone=False):
|
|
"""
|
|
Creates an XSD dateTime/date instance from a datetime.timedelta related to
|
|
0001-01-01T00:00:00 CE. In case of a date the time part is not counted.
|
|
|
|
:param delta: a datetime.timedelta instance.
|
|
:param adjust_timezone: if `True` adjust the timezone of Date objects \
|
|
with eventually present hours and minutes.
|
|
"""
|
|
try:
|
|
dt = datetime.datetime(1, 1, 1) + delta
|
|
except OverflowError:
|
|
days = delta.days
|
|
if days > 0:
|
|
y400, days = divmod(days, DAYS_IN_400Y)
|
|
y100, days = divmod(days, DAYS_IN_100Y)
|
|
y4, days = divmod(days, DAYS_IN_4Y)
|
|
y1, days = divmod(days, 365)
|
|
year = y400 * 400 + y100 * 100 + y4 * 4 + y1 + 1
|
|
if y1 == 4 or y100 == 4:
|
|
year -= 1
|
|
days = 365
|
|
|
|
td = datetime.timedelta(days=days, seconds=delta.seconds, microseconds=delta.microseconds)
|
|
dt = datetime.datetime(4 if isleap(year) else 6, 1, 1) + td
|
|
|
|
elif days >= -366:
|
|
year = -1
|
|
td = datetime.timedelta(days=days, seconds=delta.seconds, microseconds=delta.microseconds)
|
|
dt = datetime.datetime(5, 1, 1) + td
|
|
|
|
else:
|
|
days = -days - 366
|
|
y400, days = divmod(days, DAYS_IN_400Y)
|
|
y100, days = divmod(days, DAYS_IN_100Y)
|
|
y4, days = divmod(days, DAYS_IN_4Y)
|
|
y1, days = divmod(days, 365)
|
|
year = -y400 * 400 - y100 * 100 - y4 * 4 - y1 - 2
|
|
if y1 == 4 or y100 == 4:
|
|
year += 1
|
|
days = 365
|
|
|
|
td = datetime.timedelta(days=-days, seconds=delta.seconds, microseconds=delta.microseconds)
|
|
if not td:
|
|
dt = datetime.datetime(4 if isleap(year + 1) else 6, 1, 1)
|
|
year += 1
|
|
else:
|
|
dt = datetime.datetime(5 if isleap(year + 1) else 7, 1, 1) + td
|
|
else:
|
|
year = dt.year
|
|
|
|
if issubclass(cls, Date10):
|
|
if adjust_timezone and dt.hour or dt.minute:
|
|
if dt.tzinfo is None:
|
|
hour, minute = dt.hour, dt.minute
|
|
else:
|
|
hour = dt.hour - dt.tzinfo.offset.hours
|
|
minute = dt.minute - dt.tzinfo.offset.minutes
|
|
|
|
if hour < 14 or hour == 14 and minute == 0:
|
|
dt = dt.replace(tzinfo=Timezone(datetime.timedelta(hours=-hour, minutes=-minute)))
|
|
else:
|
|
dt = dt.replace(tzinfo=Timezone(datetime.timedelta(hours=-dt.hour + 24, minutes=-minute)))
|
|
dt += datetime.timedelta(days=1)
|
|
|
|
return cls(year, dt.month, dt.day, tzinfo=dt.tzinfo)
|
|
return cls(year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.microsecond, dt.tzinfo)
|
|
|
|
def todelta(self):
|
|
"""Returns the datetime.timedelta from 0001-01-01T00:00:00 CE."""
|
|
if self._year is None:
|
|
return self._dt - datetime.datetime(1, 1, 1)
|
|
|
|
year, dt = self.year, self._dt
|
|
tzinfo = None if dt.tzinfo is None else self._utc_timezone
|
|
|
|
if year > 0:
|
|
m_days = MONTH_DAYS_LEAP if isleap(year) else MONTH_DAYS
|
|
days = days_from_common_era(year - 1) + sum(m_days[m] for m in range(1, dt.month))
|
|
else:
|
|
m_days = MONTH_DAYS_LEAP if isleap(year + 1) else MONTH_DAYS
|
|
days = days_from_common_era(year) + sum(m_days[m] for m in range(1, dt.month))
|
|
|
|
delta = (dt - datetime.datetime(dt.year, dt.month, day=1, tzinfo=tzinfo))
|
|
return datetime.timedelta(days=days, seconds=delta.total_seconds())
|
|
|
|
def _date_operator(self, op, other):
|
|
if isinstance(other, self.__class__):
|
|
dt1, dt2 = self._get_operands(other)
|
|
if self._year is None and other._year is None:
|
|
return DayTimeDuration.fromtimedelta(dt1 - dt2)
|
|
return DayTimeDuration.fromtimedelta(self.todelta() - other.todelta())
|
|
|
|
elif isinstance(other, (DayTimeDuration, datetime.timedelta)):
|
|
delta = other.get_timedelta() if isinstance(other, DayTimeDuration) else other
|
|
try:
|
|
dt = op(self._dt, delta)
|
|
except OverflowError:
|
|
seconds = delta.total_seconds()
|
|
if self.bce ^ (seconds > 0):
|
|
raise
|
|
dt_seconds = abs((self._dt - datetime.datetime(1, 1, 1)).total_seconds() - seconds)
|
|
dt = datetime.datetime(1, 1, 1) + datetime.timedelta(seconds=dt_seconds)
|
|
bce = not self.bce
|
|
else:
|
|
bce = self.bce
|
|
|
|
if 'hour' not in self._pattern.groupindex.keys():
|
|
dt = dt.replace(hour=0, minute=0, second=0, microsecond=0)
|
|
|
|
elif isinstance(other, YearMonthDuration):
|
|
month = op(self._dt.month - 1, other.months) % 12 + 1
|
|
year = op(self._dt.year, ((self._dt.month - 1) + other.months) // 12)
|
|
if year > 0:
|
|
bce = self.bce
|
|
else:
|
|
bce = not self.bce
|
|
year = abs(year) + 1
|
|
dt = self._dt.replace(year=year, month=month, day=adjust_day(year, month, self._dt.day))
|
|
|
|
else:
|
|
raise ElementPathTypeError("wrong type %r for operand %r." % (type(other), other))
|
|
|
|
kwargs = {k: getattr(dt, k) for k in self._pattern.groupindex.keys()}
|
|
if bce:
|
|
kwargs['year'] = -kwargs['year']
|
|
return type(self)(**kwargs)
|
|
|
|
def __lt__(self, other):
|
|
dt1, dt2 = self._get_operands(other)
|
|
y1, y2 = self.year, other.year
|
|
return y1 < y2 or y1 == y2 and dt1 < dt2
|
|
|
|
def __le__(self, other):
|
|
dt1, dt2 = self._get_operands(other)
|
|
y1, y2 = self.year, other.year
|
|
return y1 < y2 or y1 == y2 and dt1 <= dt2
|
|
|
|
def __gt__(self, other):
|
|
dt1, dt2 = self._get_operands(other)
|
|
y1, y2 = self.year, other.year
|
|
return y1 > y2 or y1 == y2 and dt1 > dt2
|
|
|
|
def __ge__(self, other):
|
|
dt1, dt2 = self._get_operands(other)
|
|
y1, y2 = self.year, other.year
|
|
return y1 > y2 or y1 == y2 and dt1 >= dt2
|
|
|
|
def __add__(self, other):
|
|
if isinstance(other, OrderedDateTime):
|
|
raise ElementPathTypeError("wrong type %r for operand %r." % (type(other), other))
|
|
return self._date_operator(operator.add, other)
|
|
|
|
def __sub__(self, other):
|
|
return self._date_operator(operator.sub, other)
|
|
|
|
|
|
class DateTime10(OrderedDateTime):
|
|
"""XSD 1.0 xs:dateTime builtin type"""
|
|
_pattern = re.compile(
|
|
r'^(?P<year>(?:-)?[0-9]*[0-9]{4})-(?P<month>[0-9]{2})-(?P<day>[0-9]{2})'
|
|
r'(T(?P<hour>[0-9]{2}):(?P<minute>[0-9]{2}):(?P<second>[0-9]{2})(?:\.(?P<microsecond>[0-9]+))?)?'
|
|
r'(?P<tzinfo>Z|[+-](?:(?:0[0-9]|1[0-3]):[0-5][0-9]|14:00))?$')
|
|
|
|
def __init__(self, year, month, day, hour=0, minute=0, second=0, microsecond=0, tzinfo=None):
|
|
super(DateTime10, self).__init__(year, month, day, hour, minute, second, microsecond, tzinfo)
|
|
|
|
def __str__(self):
|
|
if self.microsecond:
|
|
return '{}-{:02}-{:02}T{:02}:{:02}:{:02}.{:06}{}'.format(
|
|
self.iso_year, self.month, self.day, self.hour, self.minute,
|
|
self.second, self.microsecond, str(self.tzinfo or '')
|
|
)
|
|
return '{}-{:02}-{:02}T{:02}:{:02}:{:02}{}'.format(
|
|
self.iso_year, self.month, self.day, self.hour, self.minute, self.second, str(self.tzinfo or '')
|
|
)
|
|
|
|
|
|
class DateTime(DateTime10):
|
|
"""XSD 1.1 xs:dateTime builtin type"""
|
|
version = '1.1'
|
|
|
|
|
|
class Date10(OrderedDateTime):
|
|
"""XSD 1.0 xs:date builtin type"""
|
|
_pattern = re.compile(r'^(?P<year>(?:-)?[0-9]*[0-9]{4})-(?P<month>[0-9]{2})-(?P<day>[0-9]{2})'
|
|
r'(?P<tzinfo>Z|[+-](?:(?:0[0-9]|1[0-3]):[0-5][0-9]|14:00))?$')
|
|
|
|
def __init__(self, year, month, day, tzinfo=None):
|
|
super(Date10, self).__init__(year, month, day, tzinfo=tzinfo)
|
|
|
|
def __str__(self):
|
|
return '{}-{:02}-{:02}{}'.format(self.iso_year, self.month, self.day, str(self.tzinfo or ''))
|
|
|
|
|
|
class Date(Date10):
|
|
"""XSD 1.1 xs:date builtin type"""
|
|
version = '1.1'
|
|
|
|
|
|
class XPathGregorianDay(AbstractDateTime):
|
|
"""xs:gDay datatype for XPath expressions"""
|
|
_pattern = re.compile(r'^---(?P<day>[0-9]{2})(?P<tzinfo>Z|[+-](?:(?:0[0-9]|1[0-3]):[0-5][0-9]|14:00))?$')
|
|
|
|
def __init__(self, day, tzinfo=None):
|
|
super(XPathGregorianDay, self).__init__(day=day, tzinfo=tzinfo)
|
|
|
|
def __str__(self):
|
|
return '---{:02}{}'.format(self.day, str(self.tzinfo or ''))
|
|
|
|
|
|
class GregorianDay(XPathGregorianDay, OrderedDateTime):
|
|
"""XSD xs:gDay builtin type"""
|
|
|
|
|
|
class XPathGregorianMonth(AbstractDateTime):
|
|
"""xs:gMonth datatype for XPath expressions"""
|
|
_pattern = re.compile(r'^--(?P<month>[0-9]{2})(?P<tzinfo>Z|[+-](?:(?:0[0-9]|1[0-3]):[0-5][0-9]|14:00))?$')
|
|
|
|
def __init__(self, month, tzinfo=None):
|
|
super(XPathGregorianMonth, self).__init__(month=month, tzinfo=tzinfo)
|
|
|
|
def __str__(self):
|
|
return '--{:02}{}'.format(self.month, str(self.tzinfo or ''))
|
|
|
|
|
|
class GregorianMonth(XPathGregorianMonth, OrderedDateTime):
|
|
"""XSD xs:gMonth builtin type"""
|
|
|
|
|
|
class XPathGregorianMonthDay(AbstractDateTime):
|
|
"""xs:gMonthDay datatype for XPath expressions"""
|
|
_pattern = re.compile(r'^--(?P<month>[0-9]{2})-(?P<day>[0-9]{2})'
|
|
r'(?P<tzinfo>Z|[+-](?:(?:0[0-9]|1[0-3]):[0-5][0-9]|14:00))?$')
|
|
|
|
def __init__(self, month, day, tzinfo=None):
|
|
super(XPathGregorianMonthDay, self).__init__(month=month, day=day, tzinfo=tzinfo)
|
|
|
|
def __str__(self):
|
|
return '--{:02}-{:02}{}'.format(self.month, self.day, str(self.tzinfo or ''))
|
|
|
|
|
|
class GregorianMonthDay(XPathGregorianMonthDay, OrderedDateTime):
|
|
"""XSD xs:gMonthDay builtin type"""
|
|
|
|
|
|
class XPathGregorianYear(AbstractDateTime):
|
|
"""xs:gYear datatype for XPath expressions"""
|
|
_pattern = re.compile(r'^(?P<year>(?:-)?[0-9]*[0-9]{4})'
|
|
r'(?P<tzinfo>Z|[+-](?:(?:0[0-9]|1[0-3]):[0-5][0-9]|14:00))?$')
|
|
|
|
def __init__(self, year, tzinfo=None):
|
|
super(XPathGregorianYear, self).__init__(year, tzinfo=tzinfo)
|
|
|
|
def __str__(self):
|
|
return '{}{}'.format(self.iso_year, str(self.tzinfo or ''))
|
|
|
|
|
|
class GregorianYear10(XPathGregorianYear, OrderedDateTime):
|
|
"""XSD 1.0 xs:gYear builtin type"""
|
|
|
|
|
|
class GregorianYear(GregorianYear10):
|
|
"""XSD 1.1 xs:gYear builtin type"""
|
|
version = '1.1'
|
|
|
|
|
|
class XPathGregorianYearMonth(AbstractDateTime):
|
|
"""xs:gYearMonth datatype for XPath expressions"""
|
|
_pattern = re.compile(r'^(?P<year>(?:-)?[0-9]*[0-9]{4})-(?P<month>[0-9]{2})'
|
|
r'(?P<tzinfo>Z|[+-](?:(?:0[0-9]|1[0-3]):[0-5][0-9]|14:00))?$')
|
|
|
|
def __init__(self, year, month, tzinfo=None):
|
|
super(XPathGregorianYearMonth, self).__init__(year, month, tzinfo=tzinfo)
|
|
|
|
def __str__(self):
|
|
return '{}-{:02}{}'.format(self.iso_year, self.month, str(self.tzinfo or ''))
|
|
|
|
|
|
class GregorianYearMonth10(XPathGregorianYearMonth, OrderedDateTime):
|
|
"""XSD 1.0 xs:gYearMonth builtin type"""
|
|
|
|
|
|
class GregorianYearMonth(GregorianYearMonth10):
|
|
"""XSD 1.1 xs:gYearMonth builtin type"""
|
|
version = '1.1'
|
|
|
|
|
|
class Time(AbstractDateTime):
|
|
"""XSD xs:time builtin type"""
|
|
_pattern = re.compile(
|
|
r'^(?P<hour>[0-9]{2}):(?P<minute>[0-9]{2}):(?P<second>[0-9]{2})(?:\.(?P<microsecond>[0-9]+))?'
|
|
r'(?P<tzinfo>Z|[+-](?:(?:0[0-9]|1[0-3]):[0-5][0-9]|14:00))?$')
|
|
|
|
def __init__(self, hour=0, minute=0, second=0, microsecond=0, tzinfo=None):
|
|
super(Time, self).__init__(hour=hour, minute=minute, second=second, microsecond=microsecond, tzinfo=tzinfo)
|
|
|
|
def __str__(self):
|
|
if self.microsecond:
|
|
return '{:02}:{:02}:{:02}.{:06}{}'.format(
|
|
self.hour, self.minute, self.second, self.microsecond, str(self.tzinfo or '')
|
|
)
|
|
return '{:02}:{:02}:{:02}{}'.format(self.hour, self.minute, self.second, str(self.tzinfo or ''))
|
|
|
|
def __lt__(self, other):
|
|
return operator.lt(*self._get_operands(other))
|
|
|
|
def __le__(self, other):
|
|
return operator.le(*self._get_operands(other))
|
|
|
|
def __gt__(self, other):
|
|
return operator.gt(*self._get_operands(other))
|
|
|
|
def __ge__(self, other):
|
|
return operator.ge(*self._get_operands(other))
|
|
|
|
def __add__(self, other):
|
|
if isinstance(other, DayTimeDuration):
|
|
dt = self._dt + other.get_timedelta()
|
|
elif isinstance(other, datetime.timedelta):
|
|
dt = self._dt + other
|
|
else:
|
|
raise ElementPathTypeError("wrong type %r for operand %r." % (type(other), other))
|
|
return Time(dt.hour, dt.minute, dt.second, dt.microsecond, dt.tzinfo)
|
|
|
|
def __sub__(self, other):
|
|
if isinstance(other, self.__class__):
|
|
delta = operator.sub(*self._get_operands(other))
|
|
return DayTimeDuration.fromtimedelta(delta)
|
|
elif isinstance(other, DayTimeDuration):
|
|
dt = self._dt - other.get_timedelta()
|
|
return Time(dt.hour, dt.minute, dt.second, dt.microsecond, dt.tzinfo)
|
|
else:
|
|
raise ElementPathTypeError("wrong type %r for operand %r." % (type(other), other))
|
|
|
|
|
|
class Duration(object):
|
|
"""
|
|
Base class for the XSD duration types.
|
|
|
|
:param months: an integer value that represents years and months.
|
|
:param seconds: a Decimal instance that represents days, hours, minutes, seconds and fractions of seconds.
|
|
"""
|
|
_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=0, seconds=0):
|
|
if seconds < 0 < months or months < 0 < seconds:
|
|
raise ElementPathValueError('signs differ: (months=%d, seconds=%d)' % (months, seconds))
|
|
self.months = months
|
|
self.seconds = decimal.Decimal(seconds)
|
|
|
|
def __repr__(self):
|
|
return '%s(months=%r, seconds=%s)' % (self.__class__.__name__, self.months, str(self.seconds))
|
|
|
|
def __str__(self):
|
|
m = abs(self.months)
|
|
years, months = m // 12, m % 12
|
|
s = abs(self.seconds)
|
|
days, hours, minutes, seconds = int(s // 86400), int(s // 3600 % 24), int(s // 60 % 60), 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' % seconds
|
|
elif value[-1] == 'P':
|
|
value += 'T0S'
|
|
return value
|
|
|
|
def __unicode__(self):
|
|
return str(self)
|
|
|
|
@classmethod
|
|
def fromstring(cls, text):
|
|
"""
|
|
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.
|
|
"""
|
|
match = cls._pattern.match(text)
|
|
if match is None:
|
|
raise ElementPathValueError('%r is not an xs:duration value.' % text)
|
|
|
|
sign, years, months, days, hours, minutes, seconds = match.groups()
|
|
seconds = decimal.Decimal(seconds or 0)
|
|
minutes = int(minutes or 0) + int(seconds // 60)
|
|
seconds = seconds % 60
|
|
hours = int(hours or 0) + minutes // 60
|
|
minutes = minutes % 60
|
|
days = int(days or 0) + hours // 24
|
|
hours = hours % 24
|
|
months = int(months or 0) + 12 * int(years 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 ElementPathValueError('months must be 0 for %r.' % cls.__name__)
|
|
return cls(seconds=seconds)
|
|
elif cls is YearMonthDuration:
|
|
if seconds:
|
|
raise ElementPathValueError('seconds must be 0 for %r.' % cls.__name__)
|
|
return cls(months=months)
|
|
return cls(months=months, seconds=seconds)
|
|
|
|
@property
|
|
def sign(self):
|
|
return '-' if self.months < 0 or self.seconds < 0 else ''
|
|
|
|
def _compare_durations(self, other, op):
|
|
"""
|
|
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 ElementPathTypeError("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):
|
|
return hash((self.months, self.seconds))
|
|
|
|
def __eq__(self, other):
|
|
if isinstance(other, self.__class__):
|
|
return self.months == other.months and self.seconds == other.seconds
|
|
else:
|
|
return other == (self.months, self.seconds)
|
|
|
|
def __ne__(self, other):
|
|
if isinstance(other, self.__class__):
|
|
return self.months != other.months or self.seconds != other.seconds
|
|
else:
|
|
return other != (self.months, self.seconds)
|
|
|
|
def __lt__(self, other):
|
|
return self._compare_durations(other, operator.lt)
|
|
|
|
def __le__(self, other):
|
|
return self == other or self._compare_durations(other, operator.le)
|
|
|
|
def __gt__(self, other):
|
|
return self._compare_durations(other, operator.gt)
|
|
|
|
def __ge__(self, other):
|
|
return self == other or self._compare_durations(other, operator.ge)
|
|
|
|
|
|
class YearMonthDuration(Duration):
|
|
|
|
def __init__(self, months=0):
|
|
super(YearMonthDuration, self).__init__(months, 0)
|
|
|
|
def __repr__(self):
|
|
return '%s(months=%r)' % (self.__class__.__name__, self.months)
|
|
|
|
def __add__(self, other):
|
|
if not isinstance(other, self.__class__):
|
|
raise ElementPathTypeError("wrong type %r for operand %r." % (type(other), other))
|
|
return YearMonthDuration(months=self.months + other.months)
|
|
|
|
def __sub__(self, other):
|
|
if not isinstance(other, self.__class__):
|
|
raise ElementPathTypeError("wrong type %r for operand %r." % (type(other), other))
|
|
return YearMonthDuration(months=self.months - other.months)
|
|
|
|
def __mul__(self, other):
|
|
if not isinstance(other, (float, int, decimal.Decimal)):
|
|
raise ElementPathTypeError("wrong type %r for operand %r." % (type(other), other))
|
|
return YearMonthDuration(months=int(float(self.months * other) + 0.5))
|
|
|
|
def __truediv__(self, other):
|
|
if isinstance(other, self.__class__):
|
|
return self.months / other.months
|
|
elif isinstance(other, (float, int, decimal.Decimal)):
|
|
return YearMonthDuration(months=int(float(self.months / other) + 0.5))
|
|
else:
|
|
raise ElementPathTypeError("wrong type %r for operand %r." % (type(other), other))
|
|
|
|
if not PY3:
|
|
def __div__(self, other):
|
|
return self.__truediv__(other)
|
|
|
|
|
|
class DayTimeDuration(Duration):
|
|
|
|
def __init__(self, seconds=0):
|
|
super(DayTimeDuration, self).__init__(0, seconds)
|
|
|
|
@classmethod
|
|
def fromtimedelta(cls, td):
|
|
return cls(seconds=decimal.Decimal('{}.{:06}'.format(td.days * 86400 + td.seconds, td.microseconds)))
|
|
|
|
def get_timedelta(self):
|
|
return datetime.timedelta(seconds=int(self.seconds), microseconds=int(self.seconds % 1 * 1000000))
|
|
|
|
def __repr__(self):
|
|
return '%s(seconds=%s)' % (self.__class__.__name__, str(self.seconds))
|
|
|
|
def __add__(self, other):
|
|
if not isinstance(other, self.__class__):
|
|
raise ElementPathTypeError("wrong type %r for operand %r." % (type(other), other))
|
|
return DayTimeDuration(seconds=self.seconds + other.seconds)
|
|
|
|
def __sub__(self, other):
|
|
if not isinstance(other, self.__class__):
|
|
raise ElementPathTypeError("wrong type %r for operand %r." % (type(other), other))
|
|
return DayTimeDuration(seconds=self.seconds - other.seconds)
|
|
|
|
def __mul__(self, other):
|
|
if not isinstance(other, (float, int, decimal.Decimal)):
|
|
raise ElementPathTypeError("wrong type %r for operand %r." % (type(other), other))
|
|
return DayTimeDuration(seconds=int(float(self.seconds * other) + 0.5))
|
|
|
|
def __truediv__(self, other):
|
|
if isinstance(other, self.__class__):
|
|
return self.seconds / other.seconds
|
|
elif isinstance(other, (float, int, decimal.Decimal)):
|
|
return DayTimeDuration(seconds=int(float(self.seconds / other) + 0.5))
|
|
else:
|
|
raise ElementPathTypeError("wrong type %r for operand %r." % (type(other), other))
|
|
|
|
if not PY3:
|
|
def __div__(self, other):
|
|
return self.__truediv__(other)
|
|
|
|
|
|
class UntypedAtomic(object):
|
|
"""
|
|
Class for xs:untypedAtomic data. Provides special methods for comparing
|
|
and converting to basic data types.
|
|
|
|
:param value: the untyped value, usually a string.
|
|
"""
|
|
def __init__(self, value):
|
|
self.value = value
|
|
|
|
def __repr__(self):
|
|
return '%s(value=%r)' % (self.__class__.__name__, self.value)
|
|
|
|
def _get_operands(self, other, force_float=True):
|
|
"""
|
|
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)
|
|
else:
|
|
return self.value, other.value
|
|
elif isinstance(other, int):
|
|
return float(self.value), other
|
|
else:
|
|
return type(other)(self.value), other
|
|
|
|
def __hash__(self):
|
|
return hash(self.value)
|
|
|
|
def __eq__(self, other):
|
|
return operator.eq(*self._get_operands(other, force_float=False))
|
|
|
|
def __ne__(self, other):
|
|
return not operator.eq(*self._get_operands(other, force_float=False))
|
|
|
|
def __lt__(self, other):
|
|
return operator.lt(*self._get_operands(other))
|
|
|
|
def __le__(self, other):
|
|
return operator.le(*self._get_operands(other))
|
|
|
|
def __gt__(self, other):
|
|
return operator.gt(*self._get_operands(other))
|
|
|
|
def __ge__(self, other):
|
|
return operator.ge(*self._get_operands(other))
|
|
|
|
def __add__(self, other):
|
|
return operator.add(*self._get_operands(other))
|
|
__radd__ = __add__
|
|
|
|
def __sub__(self, other):
|
|
return operator.sub(*self._get_operands(other))
|
|
|
|
def __rsub__(self, other):
|
|
return operator.sub(*reversed(self._get_operands(other)))
|
|
|
|
def __mul__(self, other):
|
|
return operator.mul(*self._get_operands(other))
|
|
__rmul__ = __mul__
|
|
|
|
def __truediv__(self, other):
|
|
return operator.truediv(*self._get_operands(other))
|
|
|
|
def __rtruediv__(self, other):
|
|
return operator.truediv(*reversed(self._get_operands(other)))
|
|
|
|
def __int__(self):
|
|
return int(self.value)
|
|
|
|
def __float__(self):
|
|
return float(self.value)
|
|
|
|
def __bool__(self):
|
|
return bool(self.value)
|
|
|
|
def __abs__(self):
|
|
return abs(self.value)
|
|
|
|
def __mod__(self, other):
|
|
return operator.mod(*self._get_operands(other))
|
|
|
|
if PY3:
|
|
def __str__(self):
|
|
return str(self.value)
|
|
|
|
def __bytes__(self):
|
|
return bytes(self.value, encoding='utf-8')
|
|
|
|
else:
|
|
def __unicode__(self):
|
|
return unicode(self.value)
|
|
|
|
def __str__(self):
|
|
try:
|
|
return str(self.value)
|
|
except UnicodeEncodeError:
|
|
return self.value.encode('utf-8')
|
|
|
|
def __bytes__(self):
|
|
return self.value.encode('utf-8')
|
|
|
|
def __div__(self, other):
|
|
return operator.truediv(*self._get_operands(other))
|
|
|
|
def __rdiv__(self, other):
|
|
return operator.truediv(*reversed(self._get_operands(other)))
|
|
|
|
def __long__(self):
|
|
return int(self.value)
|
|
|
|
|
|
####
|
|
# XSD atomic builtins validators and values
|
|
|
|
XsdBuiltin = namedtuple('XsdBuiltin', 'validator value')
|
|
"""A namedtuple-based type for describing XSD builtin types."""
|
|
|
|
WHITESPACES_PATTERN = re.compile(r'\s+')
|
|
NMTOKEN_PATTERN = re.compile(r'^[\w.\-:]+$', flags=0 if PY3 else re.U)
|
|
NAME_PATTERN = re.compile(r'^(?:[^\d\W]|:)[\w.\-:]*$', flags=0 if PY3 else re.U)
|
|
NCNAME_PATTERN = re.compile(r'^[^\d\W][\w.\-]*$', flags=0 if PY3 else re.U)
|
|
QNAME_PATTERN = re.compile(
|
|
r'^(?:(?P<prefix>[^\d\W][\w\-.\xb7\u0387\u06DD\u06DE]*):)?(?P<local>[^\d\W][\w\-.\xb7\u0387\u06DD\u06DE]*)$',
|
|
flags=0 if PY3 else re.U
|
|
)
|
|
HEX_BINARY_PATTERN = re.compile(r'^[0-9a-fA-F]+$')
|
|
NOT_BASE64_BINARY_PATTERN = re.compile(r'[^0-9a-zA-z+/= \t\n]')
|
|
LANGUAGE_CODE_PATTERN = re.compile(r'^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$')
|
|
WRONG_ESCAPE_PATTERN = re.compile(r'%(?![a-eA-E\d]{2})')
|
|
|
|
|
|
class TypeProxyMeta(type):
|
|
"""
|
|
A metaclass for creating type proxy classes, that can be used for instance
|
|
and subclass checking and for building instances of related types. A type
|
|
proxy class has to implement three methods as concrete class/static methods.
|
|
"""
|
|
def __instancecheck__(cls, instance):
|
|
return cls.instance_check(instance)
|
|
|
|
def __subclasscheck__(cls, subclass):
|
|
return cls.subclass_check(subclass)
|
|
|
|
def __call__(cls, *args, **kwargs):
|
|
return cls.instance_build(*args, **kwargs)
|
|
|
|
def instance_check(cls, instance):
|
|
"""Checks the if the argument is an instance of one of related types."""
|
|
raise NotImplementedError
|
|
|
|
def subclass_check(cls, subclass):
|
|
"""Checks the if the argument is a subclass of one of related types."""
|
|
raise NotImplementedError
|
|
|
|
def instance_build(cls, *args, **kwargs):
|
|
"""Builds an instance belonging to one of related types."""
|
|
raise NotImplementedError
|
|
|
|
|
|
@add_metaclass(TypeProxyMeta)
|
|
class NumericTypeProxy(object):
|
|
"""
|
|
A type proxy class for xs:numeric related types (xs:float, xs:decimal and
|
|
derived types). Builds xs:float instances.
|
|
"""
|
|
@staticmethod
|
|
def instance_check(other):
|
|
return isinstance(other, (int, float, decimal.Decimal)) and not isinstance(other, bool)
|
|
|
|
@staticmethod
|
|
def subclass_check(subclass):
|
|
if issubclass(subclass, bool):
|
|
return False
|
|
return issubclass(subclass, int) or issubclass(subclass, float) or issubclass(subclass, decimal.Decimal)
|
|
|
|
@staticmethod
|
|
def instance_build(x=0):
|
|
return float(x)
|
|
|
|
|
|
@add_metaclass(TypeProxyMeta)
|
|
class ArithmeticTypeProxy(object):
|
|
"""
|
|
A type proxy class for XSD types related to arithmetic operators, including
|
|
types related to xs:numeric and datetime or duration types. Builds xs:float
|
|
instances.
|
|
"""
|
|
|
|
@staticmethod
|
|
def instance_check(other):
|
|
return isinstance(other, (int, float, decimal.Decimal, AbstractDateTime, Duration)) \
|
|
and not isinstance(other, bool)
|
|
|
|
@staticmethod
|
|
def subclass_check(subclass):
|
|
if issubclass(subclass, bool):
|
|
return False
|
|
return issubclass(subclass, int) or issubclass(subclass, float) or \
|
|
issubclass(subclass, decimal.Decimal) or issubclass(subclass, Duration) \
|
|
or issubclass(subclass, AbstractDateTime)
|
|
|
|
@staticmethod
|
|
def instance_build(x=0):
|
|
return float(x)
|
|
|
|
|
|
def decimal_validator(x):
|
|
return isinstance(x, (int, decimal.Decimal)) and not isinstance(x, bool)
|
|
|
|
|
|
def integer_validator(x):
|
|
return isinstance(x, int) and not isinstance(x, bool)
|
|
|
|
|
|
def base64_binary_validator(x):
|
|
if not isinstance(x, string_base_type) or NOT_BASE64_BINARY_PATTERN.match(x) is None:
|
|
return False
|
|
try:
|
|
base64.standard_b64decode(x)
|
|
except (ValueError, TypeError):
|
|
return False
|
|
else:
|
|
return True
|
|
|
|
|
|
def hex_binary_validator(x):
|
|
return isinstance(x, string_base_type) and not len(x) % 2 and HEX_BINARY_PATTERN.match(x) is not None
|
|
|
|
|
|
def ncname_validator(x):
|
|
return isinstance(x, string_base_type) and NCNAME_PATTERN.match(x) is not None
|
|
|
|
|
|
XSD_BUILTIN_TYPES = {
|
|
'anyType': XsdBuiltin(
|
|
lambda x: True,
|
|
value=UntypedAtomic('1')
|
|
),
|
|
'anySimpleType': XsdBuiltin(
|
|
lambda x: isinstance(x, (string_base_type, int, float, bool, decimal.Decimal,
|
|
AbstractDateTime, Duration, Timezone, UntypedAtomic)),
|
|
value=UntypedAtomic('1')
|
|
),
|
|
'anyAtomicType': XsdBuiltin(
|
|
lambda x: False,
|
|
value=None
|
|
),
|
|
'string': XsdBuiltin(
|
|
lambda x: isinstance(x, string_base_type),
|
|
value=' alpha\t'
|
|
),
|
|
'decimal': XsdBuiltin(
|
|
decimal_validator,
|
|
value=decimal.Decimal('1.0')
|
|
),
|
|
'double': XsdBuiltin(
|
|
lambda x: isinstance(x, float),
|
|
value=1.0
|
|
),
|
|
'float': XsdBuiltin(
|
|
lambda x: isinstance(x, float),
|
|
value=1.0
|
|
),
|
|
'date': XsdBuiltin(
|
|
lambda x: isinstance(x, Date),
|
|
value=Date.fromstring('2000-01-01')
|
|
),
|
|
'dateTime': XsdBuiltin(
|
|
lambda x: isinstance(x, DateTime),
|
|
value=DateTime.fromstring('2000-01-01T12:00:00')
|
|
),
|
|
'gDay': XsdBuiltin(
|
|
lambda x: isinstance(x, GregorianDay),
|
|
value=GregorianDay.fromstring('---31')
|
|
),
|
|
'gMonth': XsdBuiltin(
|
|
lambda x: isinstance(x, GregorianMonth),
|
|
value=GregorianMonth.fromstring('--12')
|
|
),
|
|
'gMonthDay': XsdBuiltin(
|
|
lambda x: isinstance(x, GregorianMonthDay),
|
|
value=GregorianMonthDay.fromstring('--12-01')
|
|
),
|
|
'gYear': XsdBuiltin(
|
|
lambda x: isinstance(x, GregorianYear),
|
|
value=GregorianYear.fromstring('1999')
|
|
),
|
|
'gYearMonth': XsdBuiltin(
|
|
lambda x: isinstance(x, GregorianYearMonth),
|
|
value=GregorianYearMonth.fromstring('1999-09')
|
|
),
|
|
'time': XsdBuiltin(
|
|
lambda x: isinstance(x, Time),
|
|
value=Time.fromstring('09:26:54')
|
|
),
|
|
'duration': XsdBuiltin(
|
|
lambda x: isinstance(x, Duration),
|
|
value=Duration.fromstring('P1MT1S')
|
|
),
|
|
'dayTimeDuration': XsdBuiltin(
|
|
lambda x: isinstance(x, DayTimeDuration),
|
|
value=DayTimeDuration.fromstring('P1DT1S')
|
|
),
|
|
'yearMonthDuration': XsdBuiltin(
|
|
lambda x: isinstance(x, YearMonthDuration),
|
|
value=YearMonthDuration.fromstring('P1Y1M')
|
|
),
|
|
'QName': XsdBuiltin(
|
|
lambda x: isinstance(x, string_base_type) and QNAME_PATTERN.match(x) is not None,
|
|
value='xs:element'
|
|
),
|
|
'NOTATION': XsdBuiltin(
|
|
lambda x: isinstance(x, string_base_type),
|
|
value='alpha'
|
|
),
|
|
'anyURI': XsdBuiltin(
|
|
lambda x: isinstance(x, string_base_type),
|
|
value='https://example.com'
|
|
),
|
|
'normalizedString': XsdBuiltin(
|
|
lambda x: isinstance(x, string_base_type) and '\t' not in x and '\r' not in x,
|
|
value=' alpha ',
|
|
),
|
|
'token': XsdBuiltin(
|
|
lambda x: isinstance(x, string_base_type) and WHITESPACES_PATTERN.match(x) is None,
|
|
value='a token'
|
|
),
|
|
'language': XsdBuiltin(
|
|
lambda x: isinstance(x, string_base_type) and LANGUAGE_CODE_PATTERN.match(x) is not None,
|
|
value='en-US'
|
|
),
|
|
'Name': XsdBuiltin(
|
|
lambda x: isinstance(x, string_base_type) and NAME_PATTERN.match(x) is not None,
|
|
value='_a.name::'
|
|
),
|
|
'NCName': XsdBuiltin(
|
|
ncname_validator,
|
|
value='nc-name'
|
|
),
|
|
'ID': XsdBuiltin(
|
|
ncname_validator,
|
|
value='id1'
|
|
),
|
|
'IDREF': XsdBuiltin(
|
|
ncname_validator,
|
|
value='id_ref1'
|
|
),
|
|
'ENTITY': XsdBuiltin(
|
|
ncname_validator,
|
|
value='entity1'
|
|
),
|
|
'NMTOKEN': XsdBuiltin(
|
|
lambda x: isinstance(x, string_base_type) and NMTOKEN_PATTERN.match(x) is not None,
|
|
value='a_token'
|
|
),
|
|
'base64Binary': XsdBuiltin(
|
|
base64_binary_validator,
|
|
value=b'YWxwaGE='
|
|
),
|
|
'hexBinary': XsdBuiltin(
|
|
hex_binary_validator,
|
|
value=b'31'
|
|
),
|
|
'dateTimeStamp': XsdBuiltin(
|
|
lambda x: isinstance(x, string_base_type),
|
|
value='2000-01-01T12:00:00+01:00'
|
|
),
|
|
'integer': XsdBuiltin(
|
|
integer_validator,
|
|
value=1
|
|
),
|
|
'long': XsdBuiltin(
|
|
lambda x: integer_validator(x) and (-2**63 <= x < 2**63),
|
|
value=1
|
|
),
|
|
'int': XsdBuiltin(
|
|
lambda x: integer_validator(x) and (-2**31 <= x < 2**31),
|
|
value=1
|
|
),
|
|
'short': XsdBuiltin(
|
|
lambda x: integer_validator(x) and (-2**15 <= x < 2**15),
|
|
value=1
|
|
),
|
|
'byte': XsdBuiltin(
|
|
lambda x: integer_validator(x) and (-2**7 <= x < 2**7),
|
|
value=1
|
|
),
|
|
'positiveInteger': XsdBuiltin(
|
|
lambda x: integer_validator(x) and x > 0,
|
|
value=1
|
|
),
|
|
'negativeInteger': XsdBuiltin(
|
|
lambda x: integer_validator(x) and x < 0,
|
|
value=-1
|
|
),
|
|
'nonPositiveInteger': XsdBuiltin(
|
|
lambda x: integer_validator(x) and x <= 0,
|
|
value=0
|
|
),
|
|
'nonNegativeInteger': XsdBuiltin(
|
|
lambda x: integer_validator(x) and x >= 0,
|
|
value=0
|
|
),
|
|
'unsignedLong': XsdBuiltin(
|
|
lambda x: integer_validator(x) and (0 <= x < 2**64),
|
|
value=1
|
|
),
|
|
'unsignedInt': XsdBuiltin(
|
|
lambda x: integer_validator(x) and (0 <= x < 2**32),
|
|
value=1
|
|
),
|
|
'unsignedShort': XsdBuiltin(
|
|
lambda x: integer_validator(x) and (0 <= x < 2**16),
|
|
value=1
|
|
),
|
|
'unsignedByte': XsdBuiltin(
|
|
lambda x: integer_validator(x) and (0 <= x < 2**8),
|
|
value=1
|
|
),
|
|
'boolean': XsdBuiltin(
|
|
lambda x: isinstance(x, bool),
|
|
value=True
|
|
),
|
|
}
|