From a1506e8334eaacc3ad414589b57ceccef61967e7 Mon Sep 17 00:00:00 2001 From: Davide Brunato Date: Sat, 23 Feb 2019 21:47:01 +0100 Subject: [PATCH] Hotfix release v1.1.5 - Fix issue #3 splitting xs:gYear type, creating an unordered base type XPathGregorianYear and then deriving GregorianYear10 also from OrderedDateTime to implement comparison operators. - Rewritten in the same way the remaining gregorian types, inverting the bases (put XSD 1.0 only types as the base). --- CHANGELOG.rst | 6 ++ elementpath/datatypes.py | 94 +++++++++++++++++++----------- elementpath/xpath2_constructors.py | 18 +++--- elementpath/xpath2_functions.py | 32 +++++----- 4 files changed, 90 insertions(+), 60 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 84315240..b6f558bc 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,11 @@ CHANGELOG ********* +`v1.1.5`_ (2019-02-23) +====================== +* Differentiated unordered XPath gregorian types from ordered types for XSD +* Fix issue #2 + `v1.1.4`_ (2019-02-21) ====================== * Implementation of a full Static Analysis Phase at parse() level @@ -105,3 +110,4 @@ handling of static context evaluation .. _v1.1.2: https://github.com/brunato/elementpath/compare/v1.1.1...v1.1.2 .. _v1.1.3: https://github.com/brunato/elementpath/compare/v1.1.2...v1.1.3 .. _v1.1.4: https://github.com/brunato/elementpath/compare/v1.1.3...v1.1.4 +.. _v1.1.5: https://github.com/brunato/elementpath/compare/v1.1.4...v1.1.5 diff --git a/elementpath/datatypes.py b/elementpath/datatypes.py index 61309c02..8a71faae 100644 --- a/elementpath/datatypes.py +++ b/elementpath/datatypes.py @@ -181,7 +181,7 @@ 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.1' + version = '1.0' _pattern = re.compile(r'^$') _utc_timezone = Timezone(datetime.timedelta(0)) @@ -238,9 +238,9 @@ def iso_year(self): """The ISO string representation of the year field.""" year = self.year if -9999 <= year < -1: - return '{:05}'.format(year + 1 if self.version == '1.1' else year) + return '{:05}'.format(year if self.version == '1.0' else year + 1) elif year == -1: - return '{:04}'.format(year + 1 if self.version == '1.1' else year) + return '{:04}'.format(year if self.version == '1.0' else year + 1) elif 0 <= year <= 9999: return '{:04}'.format(year) else: @@ -306,7 +306,7 @@ def fromstring(cls, datetime_string, tzinfo=None): 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.1': + if year is not None and year <= 0 and cls.version != '1.0': kwargs['year'] -= 1 return cls(**kwargs) @@ -415,7 +415,7 @@ def fromdelta(cls, delta, adjust_timezone=False): else: year = dt.year - if issubclass(cls, Date): + if issubclass(cls, Date10): if adjust_timezone and dt.hour or dt.minute: if dt.tzinfo is None: hour, minute = dt.hour, dt.minute @@ -521,15 +521,15 @@ def __sub__(self, other): return self._date_operator(operator.sub, other) -class DateTime(OrderedDateTime): - """Class for representing xs:dateTime data.""" +class DateTime10(OrderedDateTime): + """XSD 1.0 xs:dateTime builtin type""" _pattern = re.compile( r'^(?P(?:-)?[0-9]*[0-9]{4})-(?P[0-9]{2})-(?P[0-9]{2})' r'(T(?P[0-9]{2}):(?P[0-9]{2}):(?P[0-9]{2})(?:\.(?P[0-9]+))?)?' r'(?PZ|[+-](?:(?: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(DateTime, self).__init__(year, month, day, hour, minute, second, microsecond, tzinfo) + super(DateTime10, self).__init__(year, month, day, hour, minute, second, microsecond, tzinfo) def __str__(self): if self.microsecond: @@ -542,94 +542,118 @@ def __str__(self): ) -class DateTime10(DateTime): - version = '1.0' +class DateTime(DateTime10): + """XSD 1.1 xs:dateTime builtin type""" + version = '1.1' -class Date(OrderedDateTime): - """Class for representing xs:date data.""" +class Date10(OrderedDateTime): + """XSD 1.0 xs:date builtin type""" _pattern = re.compile(r'^(?P(?:-)?[0-9]*[0-9]{4})-(?P[0-9]{2})-(?P[0-9]{2})' r'(?PZ|[+-](?:(?:0[0-9]|1[0-3]):[0-5][0-9]|14:00))?$') def __init__(self, year, month, day, tzinfo=None): - super(Date, self).__init__(year, month, day, tzinfo=tzinfo) + 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 Date10(Date): - version = '1.0' +class Date(Date10): + """XSD 1.1 xs:date builtin type""" + version = '1.1' -class GregorianDay(AbstractDateTime): - """Class for representing xs:gDay data.""" +class XPathGregorianDay(AbstractDateTime): + """xs:gDay datatype for XPath expressions""" _pattern = re.compile(r'^---(?P[0-9]{2})(?PZ|[+-](?:(?:0[0-9]|1[0-3]):[0-5][0-9]|14:00))?$') def __init__(self, day, tzinfo=None): - super(GregorianDay, self).__init__(day=day, tzinfo=tzinfo) + super(XPathGregorianDay, self).__init__(day=day, tzinfo=tzinfo) def __str__(self): return '---{:02}{}'.format(self.day, str(self.tzinfo or '')) -class GregorianMonth(AbstractDateTime): - """Class for representing xs:gMonth data.""" +class GregorianDay(XPathGregorianDay, OrderedDateTime): + """XSD xs:gDay builtin type""" + + +class XPathGregorianMonth(AbstractDateTime): + """xs:gMonth datatype for XPath expressions""" _pattern = re.compile(r'^--(?P[0-9]{2})(?PZ|[+-](?:(?:0[0-9]|1[0-3]):[0-5][0-9]|14:00))?$') def __init__(self, month, tzinfo=None): - super(GregorianMonth, self).__init__(month=month, tzinfo=tzinfo) + super(XPathGregorianMonth, self).__init__(month=month, tzinfo=tzinfo) def __str__(self): return '--{:02}{}'.format(self.month, str(self.tzinfo or '')) -class GregorianMonthDay(AbstractDateTime): - """Class for representing xs:gMonthDay data.""" +class GregorianMonth(XPathGregorianMonth, OrderedDateTime): + """XSD xs:gMonth builtin type""" + + +class XPathGregorianMonthDay(AbstractDateTime): + """xs:gMonthDay datatype for XPath expressions""" _pattern = re.compile(r'^--(?P[0-9]{2})-(?P[0-9]{2})' r'(?PZ|[+-](?:(?:0[0-9]|1[0-3]):[0-5][0-9]|14:00))?$') def __init__(self, month, day, tzinfo=None): - super(GregorianMonthDay, self).__init__(month=month, day=day, tzinfo=tzinfo) + 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 GregorianYear(AbstractDateTime): - """Class for representing xs:gYear data.""" +class GregorianMonthDay(XPathGregorianMonthDay, OrderedDateTime): + """XSD xs:gMonthDay builtin type""" + + +class XPathGregorianYear(AbstractDateTime): + """xs:gYear datatype for XPath expressions""" _pattern = re.compile(r'^(?P(?:-)?[0-9]*[0-9]{4})' r'(?PZ|[+-](?:(?:0[0-9]|1[0-3]):[0-5][0-9]|14:00))?$') def __init__(self, year, tzinfo=None): - super(GregorianYear, self).__init__(year, tzinfo=tzinfo) + super(XPathGregorianYear, self).__init__(year, tzinfo=tzinfo) def __str__(self): return '{}{}'.format(self.iso_year, str(self.tzinfo or '')) -class GregorianYear10(GregorianYear): - version = '1.0' +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 GregorianYearMonth(AbstractDateTime): - """Class for representing xs:gYearMonth data.""" +class XPathGregorianYearMonth(AbstractDateTime): + """xs:gYearMonth datatype for XPath expressions""" _pattern = re.compile(r'^(?P(?:-)?[0-9]*[0-9]{4})-(?P[0-9]{2})' r'(?PZ|[+-](?:(?:0[0-9]|1[0-3]):[0-5][0-9]|14:00))?$') def __init__(self, year, month, tzinfo=None): - super(GregorianYearMonth, self).__init__(year, month, tzinfo=tzinfo) + 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(GregorianYearMonth): - version = '1.0' +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): - """Class for representing xs:time data.""" + """XSD xs:time builtin type""" _pattern = re.compile( r'^(?P[0-9]{2}):(?P[0-9]{2}):(?P[0-9]{2})(?:\.(?P[0-9]+))?' r'(?PZ|[+-](?:(?:0[0-9]|1[0-3]):[0-5][0-9]|14:00))?$') diff --git a/elementpath/xpath2_constructors.py b/elementpath/xpath2_constructors.py index 62d1daf1..79e33a6f 100644 --- a/elementpath/xpath2_constructors.py +++ b/elementpath/xpath2_constructors.py @@ -17,10 +17,10 @@ from .compat import unicode_type, urlparse, URLError, string_base_type from .exceptions import ElementPathError, xpath_error from .xpath_helpers import is_attribute_node, boolean_value, string_value -from .datatypes import DateTime10, Date10, Time, GregorianDay, GregorianMonth, GregorianMonthDay, \ - GregorianYear10, GregorianYearMonth10, UntypedAtomic, Duration, YearMonthDuration, DayTimeDuration, \ - WHITESPACES_PATTERN, QNAME_PATTERN, NMTOKEN_PATTERN, NAME_PATTERN, NCNAME_PATTERN, HEX_BINARY_PATTERN, \ - NOT_BASE64_BINARY_PATTERN, LANGUAGE_CODE_PATTERN, WRONG_ESCAPE_PATTERN +from .datatypes import DateTime10, Date10, Time, XPathGregorianDay, XPathGregorianMonth, \ + XPathGregorianMonthDay, XPathGregorianYear, XPathGregorianYearMonth, UntypedAtomic, Duration, \ + YearMonthDuration, DayTimeDuration, WHITESPACES_PATTERN, QNAME_PATTERN, NMTOKEN_PATTERN, NAME_PATTERN, \ + NCNAME_PATTERN, HEX_BINARY_PATTERN, NOT_BASE64_BINARY_PATTERN, LANGUAGE_CODE_PATTERN, WRONG_ESCAPE_PATTERN from .xpath2_functions import XPath2Parser @@ -217,27 +217,27 @@ def cast(value, tz=None): @constructor('gDay') def cast(value, tz=None): - return GregorianDay.fromstring(value, tzinfo=tz) + return XPathGregorianDay.fromstring(value, tzinfo=tz) @constructor('gMonth') def cast(value, tz=None): - return GregorianMonth.fromstring(value, tzinfo=tz) + return XPathGregorianMonth.fromstring(value, tzinfo=tz) @constructor('gMonthDay') def cast(value, tz=None): - return GregorianMonthDay.fromstring(value, tzinfo=tz) + return XPathGregorianMonthDay.fromstring(value, tzinfo=tz) @constructor('gYear') def cast(value, tz=None): - return GregorianYear10.fromstring(value, tzinfo=tz) + return XPathGregorianYear.fromstring(value, tzinfo=tz) @constructor('gYearMonth') def cast(value, tz=None): - return GregorianYearMonth10.fromstring(value, tzinfo=tz) + return XPathGregorianYearMonth.fromstring(value, tzinfo=tz) @constructor('time') diff --git a/elementpath/xpath2_functions.py b/elementpath/xpath2_functions.py index 672cc96c..3772d6c1 100644 --- a/elementpath/xpath2_functions.py +++ b/elementpath/xpath2_functions.py @@ -21,7 +21,7 @@ import unicodedata from .compat import PY3, string_base_type, unicode_chr, urlparse, urljoin, urllib_quote, unicode_type -from .datatypes import QNAME_PATTERN, DateTime, Date, Time, Timezone, Duration, DayTimeDuration +from .datatypes import QNAME_PATTERN, DateTime10, Date10, Time, Timezone, Duration, DayTimeDuration from .namespaces import prefixed_to_qname, get_namespace from .xpath_context import XPathSchemaContext from .xpath_helpers import is_document_node, is_xpath_node, is_element_node, is_attribute_node, \ @@ -817,67 +817,67 @@ def evaluate(self, context=None): @method(function('year-from-dateTime', nargs=1)) def evaluate(self, context=None): - item = self.get_argument(context, cls=DateTime) + item = self.get_argument(context, cls=DateTime10) return [] if item is None else -(item.year + 1) if item.bce else item.year @method(function('month-from-dateTime', nargs=1)) def evaluate(self, context=None): - item = self.get_argument(context, cls=DateTime) + item = self.get_argument(context, cls=DateTime10) return [] if item is None else item.month @method(function('day-from-dateTime', nargs=1)) def evaluate(self, context=None): - item = self.get_argument(context, cls=DateTime) + item = self.get_argument(context, cls=DateTime10) return [] if item is None else item.day @method(function('hours-from-dateTime', nargs=1)) def evaluate(self, context=None): - item = self.get_argument(context, cls=DateTime) + item = self.get_argument(context, cls=DateTime10) return [] if item is None else item.hour @method(function('minutes-from-dateTime', nargs=1)) def evaluate(self, context=None): - item = self.get_argument(context, cls=DateTime) + item = self.get_argument(context, cls=DateTime10) return [] if item is None else item.minute @method(function('seconds-from-dateTime', nargs=1)) def evaluate(self, context=None): - item = self.get_argument(context, cls=DateTime) + item = self.get_argument(context, cls=DateTime10) return [] if item is None else item.second @method(function('timezone-from-dateTime', nargs=1)) def evaluate(self, context=None): - item = self.get_argument(context, cls=DateTime) + item = self.get_argument(context, cls=DateTime10) return [] if item is None else DayTimeDuration(seconds=item.tzinfo.offset.total_seconds()) @method(function('year-from-date', nargs=1)) def evaluate(self, context=None): - item = self.get_argument(context, cls=Date) + item = self.get_argument(context, cls=Date10) return [] if item is None else item.year @method(function('month-from-date', nargs=1)) def evaluate(self, context=None): - item = self.get_argument(context, cls=Date) + item = self.get_argument(context, cls=Date10) return [] if item is None else item.month @method(function('day-from-date', nargs=1)) def evaluate(self, context=None): - item = self.get_argument(context, cls=Date) + item = self.get_argument(context, cls=Date10) return [] if item is None else item.day @method(function('timezone-from-date', nargs=1)) def evaluate(self, context=None): - item = self.get_argument(context, cls=Date) + item = self.get_argument(context, cls=Date10) return [] if item is None else DayTimeDuration(seconds=item.tzinfo.offset.total_seconds()) @@ -909,12 +909,12 @@ def evaluate(self, context=None): # Timezone adjustment functions @method(function('adjust-dateTime-to-timezone', nargs=(1, 2))) def evaluate(self, context=None): - return self.adjust_datetime(context, DateTime) + return self.adjust_datetime(context, DateTime10) @method(function('adjust-date-to-timezone', nargs=(1, 2))) def evaluate(self, context=None): - return self.adjust_datetime(context, Date) + return self.adjust_datetime(context, Date10) @method(function('adjust-time-to-timezone', nargs=(1, 2))) @@ -927,13 +927,13 @@ def evaluate(self, context=None): @method(function('current-dateTime', nargs=0)) def evaluate(self, context=None): dt = datetime.datetime.now() if context is None else context.current_dt - return DateTime(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.microsecond, dt.tzinfo) + return DateTime10(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.microsecond, dt.tzinfo) @method(function('current-date', nargs=0)) def evaluate(self, context=None): dt = datetime.datetime.now() if context is None else context.current_dt - return Date(dt.year, dt.month, dt.day, tzinfo=dt.tzinfo) + return Date10(dt.year, dt.month, dt.day, tzinfo=dt.tzinfo) @method(function('current-time', nargs=0))