Update tests and docs
- Add 2019 to copyright information - Update Shphinx docs about XPath 2 status - Tests splitted into several modules (all tests are runned as before by the script test_elementpath.py or by "python -m unitest")
This commit is contained in:
parent
af65450764
commit
c03e88906f
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c), 2018, SISSA (Scuola Internazionale Superiore di Studi Avanzati -
|
||||
Copyright (c), 2018-2019, SISSA (Scuola Internazionale Superiore di Studi Avanzati -
|
||||
International School for Advanced Studies).
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
|
|
|
@ -11,9 +11,6 @@ data structures, both for the standard ElementTree library and for the
|
|||
For `lxml.etree <http://lxml.de>`_ this package can be useful for providing XPath 2.0 selectors,
|
||||
because `lxml.etree <http://lxml.de>`_ already has it's own implementation of XPath 1.0.
|
||||
|
||||
The XPath 2.0 functions implementation is not completed yet, due to wide number of functions that this
|
||||
language provides. If you want you can contribute to add an unimplemented function see the section below.
|
||||
|
||||
|
||||
Installation and usage
|
||||
======================
|
||||
|
|
|
@ -25,7 +25,7 @@ sys.path.insert(0, os.path.abspath('..'))
|
|||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = 'elementpath'
|
||||
copyright = '2018, Davide Brunato'
|
||||
copyright = '2018-2019, SISSA (International School for Advanced Studies)'
|
||||
author = 'Davide Brunato'
|
||||
|
||||
# The short X.Y version
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c), 2018, SISSA (International School for Advanced Studies).
|
||||
# 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
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c), 2018, SISSA (International School for Advanced Studies).
|
||||
# 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
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c), 2018, SISSA (International School for Advanced Studies).
|
||||
# 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
|
||||
|
@ -650,6 +650,14 @@ class Duration(object):
|
|||
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
|
||||
|
@ -705,10 +713,8 @@ class Duration(object):
|
|||
|
||||
class YearMonthDuration(Duration):
|
||||
|
||||
def __init__(self, months=0, seconds=0):
|
||||
super(YearMonthDuration, self).__init__(months, seconds)
|
||||
if self.seconds:
|
||||
raise ElementPathValueError('seconds must be 0 for %r.' % self.__class__.__name__)
|
||||
def __init__(self, months=0):
|
||||
super(YearMonthDuration, self).__init__(months, 0)
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(months=%r)' % (self.__class__.__name__, self.months)
|
||||
|
@ -743,10 +749,8 @@ class YearMonthDuration(Duration):
|
|||
|
||||
class DayTimeDuration(Duration):
|
||||
|
||||
def __init__(self, months=0, seconds=0):
|
||||
super(DayTimeDuration, self).__init__(months, seconds)
|
||||
if self.months:
|
||||
raise ElementPathValueError('months must be 0 for %r.' % self.__class__.__name__)
|
||||
def __init__(self, seconds=0):
|
||||
super(DayTimeDuration, self).__init__(0, seconds)
|
||||
|
||||
@classmethod
|
||||
def fromtimedelta(cls, td):
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c), 2018, SISSA (International School for Advanced Studies).
|
||||
# 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
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c), 2018, SISSA (International School for Advanced Studies).
|
||||
# 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
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c), 2018, SISSA (International School for Advanced Studies).
|
||||
# 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
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c), 2018, SISSA (International School for Advanced Studies).
|
||||
# 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
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c), 2018, SISSA (International School for Advanced Studies).
|
||||
# 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
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c), 2018, SISSA (International School for Advanced Studies).
|
||||
# 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
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c), 2018, SISSA (International School for Advanced Studies).
|
||||
# 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
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c), 2018, SISSA (International School for Advanced Studies).
|
||||
# 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
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c), 2018, SISSA (International School for Advanced Studies).
|
||||
# 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
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c), 2018, SISSA (International School for Advanced Studies).
|
||||
# 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
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c), 2018, SISSA (International School for Advanced Studies).
|
||||
# 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
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c), 2018, SISSA (International School for Advanced Studies).
|
||||
# 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
|
||||
|
|
2
setup.py
2
setup.py
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c), 2018, SISSA (International School for Advanced Studies).
|
||||
# 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
|
||||
|
|
|
@ -0,0 +1,522 @@
|
|||
#!/usr/bin/env 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>
|
||||
#
|
||||
import unittest
|
||||
import sys
|
||||
import datetime
|
||||
import operator
|
||||
from decimal import Decimal
|
||||
from elementpath.datatypes import months2days, DateTime, DateTime10, Date, Date10, Time, Timezone, \
|
||||
Duration, DayTimeDuration, YearMonthDuration, UntypedAtomic, GregorianYear, GregorianYear10, \
|
||||
GregorianYearMonth, GregorianYearMonth10, GregorianMonthDay, GregorianMonth, GregorianDay, \
|
||||
AbstractDateTime, OrderedDateTime
|
||||
|
||||
|
||||
class UntypedAtomicTest(unittest.TestCase):
|
||||
|
||||
def test_eq(self):
|
||||
self.assertTrue(UntypedAtomic(-10) == UntypedAtomic(-10))
|
||||
self.assertTrue(UntypedAtomic(5.2) == UntypedAtomic(5.2))
|
||||
self.assertTrue(UntypedAtomic('-6.09') == UntypedAtomic('-6.09'))
|
||||
self.assertTrue(UntypedAtomic(Decimal('8.91')) == UntypedAtomic(Decimal('8.91')))
|
||||
self.assertTrue(UntypedAtomic(False) == UntypedAtomic(False))
|
||||
|
||||
self.assertTrue(UntypedAtomic(-10) == -10)
|
||||
self.assertTrue(-10 == UntypedAtomic(-10))
|
||||
self.assertTrue('-10' == UntypedAtomic(-10))
|
||||
self.assertTrue(bool(False) == UntypedAtomic(False))
|
||||
self.assertTrue(Decimal('8.91') == UntypedAtomic(Decimal('8.91')))
|
||||
self.assertTrue(UntypedAtomic(Decimal('8.91')) == Decimal('8.91'))
|
||||
|
||||
self.assertFalse(bool(False) == UntypedAtomic(10))
|
||||
self.assertFalse(-10.9 == UntypedAtomic(-10))
|
||||
self.assertFalse(UntypedAtomic(-10) == -11)
|
||||
|
||||
self.assertFalse(UntypedAtomic(-10.5) == UntypedAtomic(-10))
|
||||
self.assertFalse(-10.5 == UntypedAtomic(-10))
|
||||
self.assertFalse(-17 == UntypedAtomic(-17.3))
|
||||
|
||||
def test_ne(self):
|
||||
self.assertTrue(UntypedAtomic(True) != UntypedAtomic(False))
|
||||
self.assertTrue(UntypedAtomic(5.12) != UntypedAtomic(5.2))
|
||||
self.assertTrue('29' != UntypedAtomic(5.2))
|
||||
self.assertFalse('2.0' != UntypedAtomic('2.0'))
|
||||
|
||||
def test_lt(self):
|
||||
self.assertTrue(UntypedAtomic(9.0) < UntypedAtomic(15))
|
||||
self.assertTrue(False < UntypedAtomic(True))
|
||||
self.assertTrue(UntypedAtomic('78') < 100.0)
|
||||
self.assertFalse(UntypedAtomic('100.1') < 100.0)
|
||||
|
||||
def test_le(self):
|
||||
self.assertTrue(UntypedAtomic(9.0) <= UntypedAtomic(15))
|
||||
self.assertTrue(False <= UntypedAtomic(False))
|
||||
self.assertTrue(UntypedAtomic('78') <= 100.0)
|
||||
self.assertFalse(UntypedAtomic('100.001') <= 100.0)
|
||||
|
||||
def test_gt(self):
|
||||
self.assertTrue(UntypedAtomic(25) > UntypedAtomic(15))
|
||||
self.assertTrue(25 > UntypedAtomic(15))
|
||||
self.assertTrue(UntypedAtomic(25) > 15)
|
||||
self.assertTrue(UntypedAtomic(25) > '15')
|
||||
|
||||
def test_ge(self):
|
||||
self.assertTrue(UntypedAtomic(25) >= UntypedAtomic(25))
|
||||
self.assertFalse(25 >= UntypedAtomic(25.1))
|
||||
|
||||
def test_conversion(self):
|
||||
self.assertEqual(str(UntypedAtomic(25.1)), '25.1')
|
||||
self.assertEqual(int(UntypedAtomic(25.1)), 25)
|
||||
self.assertEqual(float(UntypedAtomic(25.1)), 25.1)
|
||||
self.assertEqual(bool(UntypedAtomic(True)), True)
|
||||
if sys.version_info >= (3,):
|
||||
self.assertEqual(str(UntypedAtomic(u'Joan Miró')), u'Joan Miró')
|
||||
else:
|
||||
self.assertEqual(unicode(UntypedAtomic(u'Joan Miró')), u'Joan Miró')
|
||||
self.assertEqual(bytes(UntypedAtomic(u'Joan Miró')), b'Joan Mir\xc3\xb3')
|
||||
|
||||
def test_numerical_operators(self):
|
||||
self.assertEqual(0.25 * UntypedAtomic(1000), 250)
|
||||
self.assertEqual(1200 - UntypedAtomic(1000.0), 200.0)
|
||||
self.assertEqual(UntypedAtomic(1000.0) - 250, 750.0)
|
||||
self.assertEqual(UntypedAtomic('1000.0') - 250, 750.0)
|
||||
self.assertEqual(UntypedAtomic('1000.0') - UntypedAtomic(250), 750.0)
|
||||
self.assertEqual(UntypedAtomic(0.75) * UntypedAtomic(100), 75)
|
||||
self.assertEqual(UntypedAtomic('0.75') * UntypedAtomic('100'), 75)
|
||||
self.assertEqual(UntypedAtomic('9.0') / UntypedAtomic('3'), 3.0)
|
||||
self.assertEqual(9.0 / UntypedAtomic('3'), 3.0)
|
||||
self.assertEqual(UntypedAtomic('15') * UntypedAtomic('4'), 60)
|
||||
|
||||
|
||||
class DateTimeTypesTest(unittest.TestCase):
|
||||
|
||||
def test_abstract_classes(self):
|
||||
self.assertRaises(TypeError, AbstractDateTime)
|
||||
self.assertRaises(TypeError, OrderedDateTime)
|
||||
|
||||
def test_init_fromstring(self):
|
||||
self.assertIsInstance(DateTime.fromstring('2000-10-07T00:00:00'), DateTime)
|
||||
self.assertIsInstance(DateTime.fromstring('-2000-10-07T00:00:00'), DateTime)
|
||||
self.assertRaises(ValueError, DateTime.fromstring, '00-10-07')
|
||||
|
||||
self.assertIsInstance(Date.fromstring('2000-10-07'), Date)
|
||||
self.assertIsInstance(Date.fromstring('-2000-10-07'), Date)
|
||||
|
||||
def test_datetime_repr(self):
|
||||
dt = DateTime.fromstring('2000-10-07')
|
||||
self.assertEqual(repr(dt), "DateTime(2000, 10, 7, 0, 0, 0)")
|
||||
self.assertEqual(str(dt), '2000-10-07T00:00:00')
|
||||
|
||||
dt = DateTime.fromstring('-0100-04-13T23:59:59')
|
||||
self.assertEqual(repr(dt), "DateTime(-101, 4, 13, 23, 59, 59)")
|
||||
self.assertEqual(str(dt), '-0100-04-13T23:59:59')
|
||||
|
||||
dt = DateTime10.fromstring('-0100-04-13T10:30:00-04:00')
|
||||
if sys.version_info >= (3, 7):
|
||||
self.assertEqual(repr(dt), "DateTime10(-100, 4, 13, 10, 30, 0, "
|
||||
"tzinfo=Timezone(datetime.timedelta(days=-1, seconds=72000)))")
|
||||
else:
|
||||
self.assertEqual(repr(dt), "DateTime10(-100, 4, 13, 10, 30, 0, "
|
||||
"tzinfo=Timezone(datetime.timedelta(-1, 72000)))")
|
||||
self.assertEqual(str(dt), '-0100-04-13T10:30:00-04:00')
|
||||
|
||||
def test_date_repr(self):
|
||||
dt = Date.fromstring('2000-10-07')
|
||||
self.assertEqual(repr(dt), "Date(2000, 10, 7)")
|
||||
self.assertEqual(str(dt), '2000-10-07')
|
||||
|
||||
dt = Date.fromstring('-0100-04-13')
|
||||
self.assertEqual(repr(dt), "Date(-101, 4, 13)")
|
||||
self.assertEqual(str(dt), '-0100-04-13')
|
||||
|
||||
dt = Date10.fromstring('-0100-04-13')
|
||||
self.assertEqual(repr(dt), "Date10(-100, 4, 13)")
|
||||
self.assertEqual(str(dt), '-0100-04-13')
|
||||
|
||||
def test_gregorian_year_repr(self):
|
||||
dt = GregorianYear.fromstring('1991')
|
||||
self.assertEqual(repr(dt), "GregorianYear(1991)")
|
||||
self.assertEqual(str(dt), '1991')
|
||||
|
||||
dt = GregorianYear.fromstring('0000')
|
||||
self.assertEqual(repr(dt), "GregorianYear(-1)")
|
||||
self.assertEqual(str(dt), '0000')
|
||||
|
||||
dt = GregorianYear10.fromstring('-0050')
|
||||
self.assertEqual(repr(dt), "GregorianYear10(-50)")
|
||||
self.assertEqual(str(dt), '-0050')
|
||||
|
||||
def test_gregorian_day_repr(self):
|
||||
dt = GregorianDay.fromstring('---31')
|
||||
self.assertEqual(repr(dt), "GregorianDay(31)")
|
||||
self.assertEqual(str(dt), '---31')
|
||||
|
||||
dt = GregorianDay.fromstring('---05Z')
|
||||
self.assertEqual(repr(dt), "GregorianDay(5, tzinfo=Timezone(datetime.timedelta(0)))")
|
||||
self.assertEqual(str(dt), '---05Z')
|
||||
|
||||
def test_gregorian_month_repr(self):
|
||||
dt = GregorianMonth.fromstring('--09')
|
||||
self.assertEqual(repr(dt), "GregorianMonth(9)")
|
||||
self.assertEqual(str(dt), '--09')
|
||||
|
||||
def test_gregorian_month_day_repr(self):
|
||||
dt = GregorianMonthDay.fromstring('--07-23')
|
||||
self.assertEqual(repr(dt), "GregorianMonthDay(7, 23)")
|
||||
self.assertEqual(str(dt), '--07-23')
|
||||
|
||||
def test_gregorian_year_month_repr(self):
|
||||
dt = GregorianYearMonth.fromstring('-1890-12')
|
||||
self.assertEqual(repr(dt), "GregorianYearMonth(-1891, 12)")
|
||||
self.assertEqual(str(dt), '-1890-12')
|
||||
|
||||
dt = GregorianYearMonth10.fromstring('-0050-04')
|
||||
self.assertEqual(repr(dt), "GregorianYearMonth10(-50, 4)")
|
||||
self.assertEqual(str(dt), '-0050-04')
|
||||
|
||||
def test_time_repr(self):
|
||||
dt = Time.fromstring('20:40:13')
|
||||
self.assertEqual(repr(dt), "Time(20, 40, 13)")
|
||||
self.assertEqual(str(dt), '20:40:13')
|
||||
|
||||
dt = Time.fromstring('24:00:00')
|
||||
self.assertEqual(repr(dt), "Time(0, 0, 0)")
|
||||
self.assertEqual(str(dt), '00:00:00')
|
||||
|
||||
def test_eq_operator(self):
|
||||
tz = Timezone.fromstring('-05:00')
|
||||
mkdt = DateTime.fromstring
|
||||
|
||||
self.assertTrue(mkdt("2002-04-02T12:00:00-01:00") == mkdt("2002-04-02T17:00:00+04:00"))
|
||||
self.assertFalse(mkdt("2002-04-02T12:00:00") == mkdt("2002-04-02T23:00:00+06:00"))
|
||||
self.assertFalse(mkdt("2002-04-02T12:00:00") == mkdt("2002-04-02T17:00:00"))
|
||||
self.assertTrue(mkdt("2002-04-02T12:00:00") == mkdt("2002-04-02T12:00:00"))
|
||||
self.assertTrue(mkdt("2002-04-02T23:00:00-04:00") == mkdt("2002-04-03T02:00:00-01:00"))
|
||||
self.assertTrue(mkdt("1999-12-31T24:00:00") == mkdt("1999-12-31T00:00:00"))
|
||||
self.assertTrue(mkdt("2005-04-04T24:00:00") == mkdt("2005-04-04T00:00:00"))
|
||||
|
||||
self.assertTrue(mkdt("2002-04-02T12:00:00-01:00", tz) == mkdt("2002-04-02T17:00:00+04:00", tz))
|
||||
self.assertTrue(mkdt("2002-04-02T12:00:00", tz) == mkdt("2002-04-02T23:00:00+06:00", tz))
|
||||
self.assertFalse(mkdt("2002-04-02T12:00:00", tz) == mkdt("2002-04-02T17:00:00", tz))
|
||||
self.assertTrue(mkdt("2002-04-02T12:00:00", tz) == mkdt("2002-04-02T12:00:00", tz))
|
||||
self.assertTrue(mkdt("2002-04-02T23:00:00-04:00", tz) == mkdt("2002-04-03T02:00:00-01:00", tz))
|
||||
self.assertTrue(mkdt("1999-12-31T24:00:00", tz) == mkdt("1999-12-31T00:00:00", tz))
|
||||
self.assertTrue(mkdt("2005-04-04T24:00:00", tz) == mkdt("2005-04-04T00:00:00", tz))
|
||||
|
||||
self.assertFalse(mkdt("2005-04-04T24:00:00", tz) != mkdt("2005-04-04T00:00:00", tz))
|
||||
|
||||
self.assertTrue(DateTime.fromstring("-1000-01-01") == DateTime.fromstring("-1000-01-01"))
|
||||
self.assertTrue(DateTime.fromstring("-10000-01-01") == DateTime.fromstring("-10000-01-01"))
|
||||
self.assertFalse(DateTime.fromstring("20000-01-01") != DateTime.fromstring("20000-01-01"))
|
||||
self.assertFalse(DateTime.fromstring("-10000-01-02") == DateTime.fromstring("-10000-01-01"))
|
||||
|
||||
def test_lt_operator(self):
|
||||
mkdt = DateTime.fromstring
|
||||
mkdate = Date.fromstring
|
||||
|
||||
self.assertTrue(mkdt("2002-04-02T12:00:00-01:00") < mkdt("2002-04-02T17:00:00-01:00"))
|
||||
self.assertFalse(mkdt("2002-04-02T18:00:00-01:00") < mkdt("2002-04-02T17:00:00-01:00"))
|
||||
self.assertTrue(mkdt("2002-04-02T18:00:00+02:00") < mkdt("2002-04-02T17:00:00Z"))
|
||||
self.assertTrue(mkdt("2002-04-02T18:00:00+02:00") < mkdt("2002-04-03T00:00:00Z"))
|
||||
self.assertTrue(mkdt("-2002-01-01T10:00:00") < mkdt("2001-01-01T17:00:00Z"))
|
||||
self.assertFalse(mkdt("2002-01-01T10:00:00") < mkdt("-2001-01-01T17:00:00Z"))
|
||||
self.assertTrue(mkdt("-2002-01-01T10:00:00") < mkdt("-2001-01-01T17:00:00Z"))
|
||||
self.assertTrue(mkdt("-12002-01-01T10:00:00") < mkdt("-12001-01-01T17:00:00Z"))
|
||||
self.assertFalse(mkdt("12002-01-01T10:00:00") < mkdt("12001-01-01T17:00:00Z"))
|
||||
self.assertTrue(mkdt("-10000-01-01T10:00:00Z") < mkdt("-10000-01-01T17:00:00Z"))
|
||||
self.assertRaises(TypeError, operator.lt, mkdt("2002-04-02T18:00:00+02:00"), mkdate("2002-04-03"))
|
||||
|
||||
def test_le_operator(self):
|
||||
mkdt = DateTime.fromstring
|
||||
mkdate = Date.fromstring
|
||||
|
||||
self.assertTrue(mkdt("2002-04-02T12:00:00-01:00") <= mkdt("2002-04-02T12:00:00-01:00"))
|
||||
self.assertFalse(mkdt("2002-04-02T18:00:00-01:00") <= mkdt("2002-04-02T17:00:00-01:00"))
|
||||
self.assertTrue(mkdt("2002-04-02T18:00:00+01:00") <= mkdt("2002-04-02T17:00:00Z"))
|
||||
self.assertTrue(mkdt("-2002-01-01T10:00:00") <= mkdt("2001-01-01T17:00:00Z"))
|
||||
self.assertFalse(mkdt("2002-01-01T10:00:00") <= mkdt("-2001-01-01T17:00:00Z"))
|
||||
self.assertTrue(mkdt("-2002-01-01T10:00:00") <= mkdt("-2001-01-01T17:00:00Z"))
|
||||
self.assertTrue(mkdt("-10000-01-01T10:00:00Z") <= mkdt("-10000-01-01T10:00:00Z"))
|
||||
self.assertTrue(mkdt("-190000-01-01T10:00:00Z") <= mkdt("0100-01-01T10:00:00Z"))
|
||||
self.assertRaises(TypeError, operator.le, mkdt("2002-04-02T18:00:00+02:00"), mkdate("2002-04-03"))
|
||||
|
||||
def test_gt_operator(self):
|
||||
mkdt = DateTime.fromstring
|
||||
mkdate = Date.fromstring
|
||||
|
||||
self.assertFalse(mkdt("2002-04-02T12:00:00-01:00") > mkdt("2002-04-02T17:00:00-01:00"))
|
||||
self.assertTrue(mkdt("2002-04-02T18:00:00-01:00") > mkdt("2002-04-02T17:00:00-01:00"))
|
||||
self.assertFalse(mkdt("2002-04-02T18:00:00+02:00") > mkdt("2002-04-02T17:00:00Z"))
|
||||
self.assertFalse(mkdt("2002-04-02T18:00:00+02:00") > mkdt("2002-04-03T00:00:00Z"))
|
||||
self.assertTrue(mkdt("2002-01-01T10:00:00") > mkdt("-2001-01-01T17:00:00Z"))
|
||||
self.assertFalse(mkdt("-2002-01-01T10:00:00") > mkdt("-2001-01-01T17:00:00Z"))
|
||||
self.assertTrue(mkdt("13567-04-18T10:00:00Z") > datetime.datetime.now())
|
||||
self.assertFalse(mkdt("15032-11-12T23:17:59Z") > mkdt("15032-11-12T23:17:59Z"))
|
||||
self.assertRaises(TypeError, operator.lt, mkdt("2002-04-02T18:00:00+02:00"), mkdate("2002-04-03"))
|
||||
|
||||
def test_ge_operator(self):
|
||||
mkdt = DateTime.fromstring
|
||||
mkdate = Date.fromstring
|
||||
|
||||
self.assertTrue(mkdt("2002-04-02T12:00:00-01:00") >= mkdt("2002-04-02T12:00:00-01:00"))
|
||||
self.assertTrue(mkdt("2002-04-02T18:00:00-01:00") >= mkdt("2002-04-02T17:00:00-01:00"))
|
||||
self.assertTrue(mkdt("2002-04-02T18:00:00+01:00") >= mkdt("2002-04-02T17:00:00Z"))
|
||||
self.assertFalse(mkdt("-2002-01-01T10:00:00") >= mkdt("2001-01-01T17:00:00Z"))
|
||||
self.assertTrue(mkdt("2002-01-01T10:00:00") >= mkdt("-2001-01-01T17:00:00Z"))
|
||||
self.assertFalse(mkdt("-2002-01-01T10:00:00") >= mkdt("-2001-01-01T17:00:00Z"))
|
||||
self.assertTrue(mkdt("-3000-06-21T00:00:00Z") >= mkdt("-3000-06-21T00:00:00Z"))
|
||||
self.assertFalse(mkdt("-3000-06-21T00:00:00Z") >= mkdt("-3000-06-21T01:00:00Z"))
|
||||
self.assertTrue(mkdt("15032-11-12T23:17:59Z") >= mkdt("15032-11-12T23:17:59Z"))
|
||||
self.assertRaises(TypeError, operator.le, mkdt("2002-04-02T18:00:00+02:00"), mkdate("2002-04-03"))
|
||||
|
||||
def test_months2days_function(self):
|
||||
# self.assertEqual(months2days(-119, 1, 12 * 319), 116512)
|
||||
self.assertEqual(months2days(200, 1, -12 * 320) - 1, -116877 - 2)
|
||||
|
||||
# 0000 BCE tests
|
||||
self.assertEqual(months2days(0, 1, 12), 366)
|
||||
self.assertEqual(months2days(0, 1, -12), -365)
|
||||
self.assertEqual(months2days(1, 1, 12), 365)
|
||||
self.assertEqual(months2days(1, 1, -12), -366)
|
||||
|
||||
def test_common_era_delta(self):
|
||||
self.assertEqual(Date.fromstring("0001-01-01").common_era_delta, datetime.timedelta(days=0))
|
||||
self.assertEqual(Date.fromstring("0001-02-01").common_era_delta, datetime.timedelta(days=31))
|
||||
self.assertEqual(Date.fromstring("0001-03-01").common_era_delta, datetime.timedelta(days=59))
|
||||
self.assertEqual(Date.fromstring("0001-06-01").common_era_delta, datetime.timedelta(days=151))
|
||||
self.assertEqual(Date.fromstring("0001-06-03").common_era_delta, datetime.timedelta(days=153))
|
||||
self.assertEqual(DateTime.fromstring("0001-06-03T20:00:00").common_era_delta,
|
||||
datetime.timedelta(days=153, seconds=72000))
|
||||
|
||||
self.assertEqual(Date.fromstring("0002-01-01").common_era_delta, datetime.timedelta(days=365))
|
||||
self.assertEqual(Date.fromstring("0002-02-01").common_era_delta, datetime.timedelta(days=396))
|
||||
|
||||
self.assertEqual(Date.fromstring("-0000-01-01").common_era_delta, datetime.timedelta(days=-366))
|
||||
self.assertEqual(Date.fromstring("-0000-02-01").common_era_delta, datetime.timedelta(days=-335))
|
||||
self.assertEqual(Date.fromstring("-0000-12-31").common_era_delta, datetime.timedelta(days=-1))
|
||||
|
||||
self.assertEqual(Date10.fromstring("-0001-01-01").common_era_delta, datetime.timedelta(days=-366))
|
||||
self.assertEqual(Date10.fromstring("-0001-02-10").common_era_delta, datetime.timedelta(days=-326))
|
||||
self.assertEqual(Date10.fromstring("-0001-12-31Z").common_era_delta, datetime.timedelta(days=-1))
|
||||
self.assertEqual(Date10.fromstring("-0001-12-31-02:00").common_era_delta, datetime.timedelta(hours=-22))
|
||||
self.assertEqual(Date10.fromstring("-0001-12-31+03:00").common_era_delta, datetime.timedelta(hours=-27))
|
||||
|
||||
def test_sub_operator(self):
|
||||
date = Date.fromstring
|
||||
date10 = Date10.fromstring
|
||||
|
||||
self.assertEqual(date("2002-04-02") - date("2002-04-01"), DayTimeDuration(seconds=86400))
|
||||
self.assertEqual(date("-2002-04-02") - date("-2002-04-01"), DayTimeDuration(seconds=86400))
|
||||
self.assertEqual(date("-0002-01-01") - date("-0001-12-31"), DayTimeDuration.fromstring('-P729D'))
|
||||
|
||||
self.assertEqual(date("-0101-01-01") - date("-0100-12-31"), DayTimeDuration.fromstring('-P729D'))
|
||||
self.assertEqual(date("15032-11-12") - date("15032-11-11"), DayTimeDuration(seconds=86400))
|
||||
self.assertEqual(date("-9999-11-12") - date("-9999-11-11"), DayTimeDuration(seconds=86400))
|
||||
self.assertEqual(date("-9999-11-12") - date("-9999-11-12"), DayTimeDuration(seconds=0))
|
||||
|
||||
self.assertEqual(date10("-2001-04-02-02:00") - date10("-2001-04-01"), DayTimeDuration.fromstring('P1DT2H'))
|
||||
|
||||
|
||||
class DurationTypesTest(unittest.TestCase):
|
||||
|
||||
def test_months2days_function(self):
|
||||
# xs:duration ordering related tests
|
||||
self.assertEqual(months2days(year=1696, month=9, months_delta=0), 0)
|
||||
self.assertEqual(months2days(1696, 9, 1), 30)
|
||||
self.assertEqual(months2days(1696, 9, 2), 61)
|
||||
self.assertEqual(months2days(1696, 9, 3), 91)
|
||||
self.assertEqual(months2days(1696, 9, 4), 122)
|
||||
self.assertEqual(months2days(1696, 9, 5), 153)
|
||||
self.assertEqual(months2days(1696, 9, 12), 365)
|
||||
self.assertEqual(months2days(1696, 9, -1), -31)
|
||||
self.assertEqual(months2days(1696, 9, -2), -62)
|
||||
self.assertEqual(months2days(1696, 9, -12), -366)
|
||||
|
||||
self.assertEqual(months2days(1697, 2, 0), 0)
|
||||
self.assertEqual(months2days(1697, 2, 1), 28)
|
||||
self.assertEqual(months2days(1697, 2, 12), 365)
|
||||
self.assertEqual(months2days(1697, 2, -1), -31)
|
||||
self.assertEqual(months2days(1697, 2, -2), -62)
|
||||
self.assertEqual(months2days(1697, 2, -3), -92)
|
||||
self.assertEqual(months2days(1697, 2, -12), -366)
|
||||
self.assertEqual(months2days(1697, 2, -14), -428)
|
||||
self.assertEqual(months2days(1697, 2, -15), -458)
|
||||
|
||||
self.assertEqual(months2days(1903, 3, 0), 0)
|
||||
self.assertEqual(months2days(1903, 3, 1), 31)
|
||||
self.assertEqual(months2days(1903, 3, 2), 61)
|
||||
self.assertEqual(months2days(1903, 3, 3), 92)
|
||||
self.assertEqual(months2days(1903, 3, 4), 122)
|
||||
self.assertEqual(months2days(1903, 3, 11), 366 - 29)
|
||||
self.assertEqual(months2days(1903, 3, 12), 366)
|
||||
self.assertEqual(months2days(1903, 3, -1), -28)
|
||||
self.assertEqual(months2days(1903, 3, -2), -59)
|
||||
self.assertEqual(months2days(1903, 3, -3), -90)
|
||||
self.assertEqual(months2days(1903, 3, -12), -365)
|
||||
|
||||
self.assertEqual(months2days(1903, 7, 0), 0)
|
||||
self.assertEqual(months2days(1903, 7, 1), 31)
|
||||
self.assertEqual(months2days(1903, 7, 2), 62)
|
||||
self.assertEqual(months2days(1903, 7, 3), 92)
|
||||
self.assertEqual(months2days(1903, 7, 6), 184)
|
||||
self.assertEqual(months2days(1903, 7, 12), 366)
|
||||
self.assertEqual(months2days(1903, 7, -1), -30)
|
||||
self.assertEqual(months2days(1903, 7, -2), -61)
|
||||
self.assertEqual(months2days(1903, 7, -6), -181)
|
||||
self.assertEqual(months2days(1903, 7, -12), -365)
|
||||
|
||||
# Extra tests
|
||||
self.assertEqual(months2days(1900, 3, 0), 0)
|
||||
self.assertEqual(months2days(1900, 3, 1), 31)
|
||||
self.assertEqual(months2days(1900, 3, 24), 730)
|
||||
self.assertEqual(months2days(1900, 3, -1), -28)
|
||||
self.assertEqual(months2days(1900, 3, -24), -730)
|
||||
|
||||
self.assertEqual(months2days(1000, 4, 0), 0)
|
||||
self.assertEqual(months2days(1000, 4, 1), 30)
|
||||
self.assertEqual(months2days(1000, 4, 24), 730)
|
||||
self.assertEqual(months2days(1000, 4, -1), -31)
|
||||
self.assertEqual(months2days(1000, 4, -24), -730)
|
||||
|
||||
self.assertEqual(months2days(2001, 10, -12), -365)
|
||||
self.assertEqual(months2days(2000, 10, -12), -366)
|
||||
self.assertEqual(months2days(2000, 2, -12), -365)
|
||||
self.assertEqual(months2days(2000, 3, -12), -366)
|
||||
|
||||
def test_init_fromstring(self):
|
||||
self.assertIsInstance(Duration.fromstring('P1Y'), Duration)
|
||||
self.assertIsInstance(Duration.fromstring('P1M'), Duration)
|
||||
self.assertIsInstance(Duration.fromstring('P1D'), Duration)
|
||||
self.assertIsInstance(Duration.fromstring('PT0H'), Duration)
|
||||
self.assertIsInstance(Duration.fromstring('PT1M'), Duration)
|
||||
self.assertIsInstance(Duration.fromstring('PT0.0S'), Duration)
|
||||
|
||||
self.assertRaises(ValueError, Duration.fromstring, 'P')
|
||||
self.assertRaises(ValueError, Duration.fromstring, 'PT')
|
||||
self.assertRaises(ValueError, Duration.fromstring, '1Y')
|
||||
self.assertRaises(ValueError, Duration.fromstring, 'P1W1DT5H3M23.9S')
|
||||
self.assertRaises(ValueError, Duration.fromstring, 'P1.5Y')
|
||||
self.assertRaises(ValueError, Duration.fromstring, 'PT1.1H')
|
||||
self.assertRaises(ValueError, Duration.fromstring, 'P1.0DT5H3M23.9S')
|
||||
|
||||
def test_as_string(self):
|
||||
self.assertEqual(str(Duration.fromstring('P3Y1D')), 'P3Y1D')
|
||||
self.assertEqual(str(Duration.fromstring('PT2M10.4S')), 'PT2M10.4S')
|
||||
self.assertEqual(str(Duration.fromstring('PT2400H')), 'P100D')
|
||||
self.assertEqual(str(Duration.fromstring('-P15M')), '-P1Y3M')
|
||||
self.assertEqual(str(Duration.fromstring('-P809YT3H5M5S')), '-P809YT3H5M5S')
|
||||
|
||||
def test_eq(self):
|
||||
self.assertEqual(Duration.fromstring('PT147.5S'), (0, 147.5))
|
||||
self.assertEqual(Duration.fromstring('PT147.3S'), (0, Decimal("147.3")))
|
||||
|
||||
self.assertEqual(Duration.fromstring('PT2M10.4S'), (0, Decimal("130.4")))
|
||||
self.assertEqual(Duration.fromstring('PT5H3M23.9S'), (0, Decimal("18203.9")))
|
||||
self.assertEqual(Duration.fromstring('P1DT5H3M23.9S'), (0, Decimal("104603.9")))
|
||||
self.assertEqual(Duration.fromstring('P31DT5H3M23.9S'), (0, Decimal("2696603.9")))
|
||||
self.assertEqual(Duration.fromstring('P1Y1DT5H3M23.9S'), (12, Decimal("104603.9")))
|
||||
|
||||
self.assertEqual(Duration.fromstring('-P809YT3H5M5S'), (-9708, -11105))
|
||||
self.assertEqual(Duration.fromstring('P15M'), (15, 0))
|
||||
self.assertEqual(Duration.fromstring('P1Y'), (12, 0))
|
||||
self.assertEqual(Duration.fromstring('P3Y1D'), (36, 3600 * 24))
|
||||
self.assertEqual(Duration.fromstring('PT2400H'), (0, 8640000))
|
||||
self.assertEqual(Duration.fromstring('PT4500M'), (0, 4500 * 60))
|
||||
self.assertEqual(Duration.fromstring('PT4500M70S'), (0, 4500 * 60 + 70))
|
||||
self.assertEqual(Duration.fromstring('PT5529615.3S'), (0, Decimal('5529615.3')))
|
||||
|
||||
def test_ne(self):
|
||||
self.assertNotEqual(Duration.fromstring('PT147.3S'), None)
|
||||
self.assertNotEqual(Duration.fromstring('PT147.3S'), (0, 147.3))
|
||||
self.assertNotEqual(Duration.fromstring('P3Y1D'), (36, 3600 * 2))
|
||||
self.assertNotEqual(Duration.fromstring('P3Y1D'), (36, 3600 * 24, 0))
|
||||
self.assertNotEqual(Duration.fromstring('P3Y1D'), None)
|
||||
|
||||
def test_lt(self):
|
||||
self.assertTrue(Duration(months=15) < Duration(months=16))
|
||||
self.assertFalse(Duration(months=16) < Duration(months=16))
|
||||
self.assertTrue(Duration(months=16) < Duration.fromstring('P16M1D'))
|
||||
self.assertTrue(Duration(months=16) < Duration.fromstring('P16MT1H'))
|
||||
self.assertTrue(Duration(months=16) < Duration.fromstring('P16MT1M'))
|
||||
self.assertTrue(Duration(months=16) < Duration.fromstring('P16MT1S'))
|
||||
self.assertFalse(Duration(months=16) < Duration.fromstring('P16MT0S'))
|
||||
|
||||
def test_le(self):
|
||||
self.assertTrue(Duration(months=15) <= Duration(months=16))
|
||||
self.assertTrue(Duration(months=16) <= Duration(16))
|
||||
self.assertTrue(Duration(months=16) <= Duration.fromstring('P16M1D'))
|
||||
self.assertTrue(Duration(months=16) <= Duration.fromstring('P16MT1H'))
|
||||
self.assertTrue(Duration(months=16) <= Duration.fromstring('P16MT1M'))
|
||||
self.assertTrue(Duration(months=16) <= Duration.fromstring('P16MT1S'))
|
||||
self.assertTrue(Duration(months=16) <= Duration.fromstring('P16MT0S'))
|
||||
|
||||
def test_gt(self):
|
||||
self.assertTrue(Duration(months=16) > Duration(15))
|
||||
self.assertFalse(Duration(months=16) > Duration(16))
|
||||
|
||||
def test_ge(self):
|
||||
self.assertTrue(Duration(16) >= Duration(15))
|
||||
self.assertTrue(Duration(16) >= Duration(16))
|
||||
self.assertTrue(Duration.fromstring('P1Y1DT1S') >= Duration.fromstring('P1Y1D'))
|
||||
|
||||
def test_incomparable_values(self):
|
||||
self.assertFalse(Duration(1) < Duration.fromstring('P30D'))
|
||||
self.assertFalse(Duration(1) <= Duration.fromstring('P30D'))
|
||||
self.assertFalse(Duration(1) > Duration.fromstring('P30D'))
|
||||
self.assertFalse(Duration(1) >= Duration.fromstring('P30D'))
|
||||
|
||||
def test_day_time_duration(self):
|
||||
self.assertEqual(DayTimeDuration(300).seconds, 300)
|
||||
|
||||
def test_year_month_duration(self):
|
||||
self.assertEqual(YearMonthDuration(10).months, 10)
|
||||
|
||||
|
||||
class TimezoneTypeTest(unittest.TestCase):
|
||||
|
||||
def test_init_format(self):
|
||||
self.assertEqual(Timezone.fromstring('Z').offset, datetime.timedelta(0))
|
||||
self.assertEqual(Timezone.fromstring('00:00').offset, datetime.timedelta(0))
|
||||
self.assertEqual(Timezone.fromstring('+00:00').offset, datetime.timedelta(0))
|
||||
self.assertEqual(Timezone.fromstring('-00:00').offset, datetime.timedelta(0))
|
||||
self.assertEqual(Timezone.fromstring('-0:0').offset, datetime.timedelta(0))
|
||||
self.assertEqual(Timezone.fromstring('+05:15').offset, datetime.timedelta(hours=5, minutes=15))
|
||||
self.assertEqual(Timezone.fromstring('-11:00').offset, datetime.timedelta(hours=-11))
|
||||
self.assertEqual(Timezone.fromstring('+13:59').offset, datetime.timedelta(hours=13, minutes=59))
|
||||
self.assertEqual(Timezone.fromstring('-13:59').offset, datetime.timedelta(hours=-13, minutes=-59))
|
||||
self.assertEqual(Timezone.fromstring('+14:00').offset, datetime.timedelta(hours=14))
|
||||
self.assertEqual(Timezone.fromstring('-14:00').offset, datetime.timedelta(hours=-14))
|
||||
|
||||
self.assertRaises(ValueError, Timezone.fromstring, '-15:00')
|
||||
self.assertRaises(ValueError, Timezone.fromstring, '-14:01')
|
||||
self.assertRaises(ValueError, Timezone.fromstring, '+14:01')
|
||||
self.assertRaises(ValueError, Timezone.fromstring, '+10')
|
||||
self.assertRaises(ValueError, Timezone.fromstring, '+10:00:00')
|
||||
|
||||
def test_init_timedelta(self):
|
||||
td0 = datetime.timedelta(0)
|
||||
td1 = datetime.timedelta(hours=5, minutes=15)
|
||||
td2 = datetime.timedelta(hours=-14, minutes=0)
|
||||
td3 = datetime.timedelta(hours=-14, minutes=-1)
|
||||
|
||||
self.assertEqual(Timezone(td0).offset, td0)
|
||||
self.assertEqual(Timezone(td1).offset, td1)
|
||||
self.assertEqual(Timezone(td2).offset, td2)
|
||||
self.assertRaises(ValueError, Timezone, td3)
|
||||
self.assertRaises(TypeError, Timezone, 0)
|
||||
|
||||
def test_as_string(self):
|
||||
self.assertEqual(str(Timezone.fromstring('+05:00')), '+05:00')
|
||||
self.assertEqual(str(Timezone.fromstring('-13:15')), '-13:15')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
File diff suppressed because it is too large
Load Diff
|
@ -1,7 +1,7 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c), 2018, SISSA (International School for Advanced Studies).
|
||||
# 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
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
#!/usr/bin/env 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>
|
||||
#
|
||||
import unittest
|
||||
import lxml.etree
|
||||
|
||||
from elementpath import *
|
||||
from elementpath.namespaces import XML_LANG_QNAME
|
||||
|
||||
try:
|
||||
# noinspection PyPackageRequirements
|
||||
import xmlschema
|
||||
except (ImportError, AttributeError):
|
||||
xmlschema = None
|
||||
|
||||
try:
|
||||
from tests import test_xpath2_parser
|
||||
except ImportError:
|
||||
# Python2 fallback
|
||||
import test_xpath2_parser
|
||||
|
||||
|
||||
@unittest.skipIf(xmlschema is None, "xmlschema library >= v1.0.7 required.")
|
||||
class XPath2ParserXMLSchemaTest(test_xpath2_parser.XPath2ParserTest):
|
||||
|
||||
if xmlschema:
|
||||
schema = XMLSchemaProxy(
|
||||
schema=xmlschema.XMLSchema('''
|
||||
<!-- Dummy schema, only for tests -->
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://xpath.test/ns">
|
||||
<xs:element name="test_element" type="xs:string"/>
|
||||
<xs:attribute name="test_attribute" type="xs:string"/>
|
||||
</xs:schema>''')
|
||||
)
|
||||
else:
|
||||
schema = None
|
||||
|
||||
def setUp(self):
|
||||
self.parser = XPath2Parser(namespaces=self.namespaces, schema=self.schema, variables=self.variables)
|
||||
|
||||
def test_xmlschema_proxy(self):
|
||||
context = XPathContext(root=self.etree.XML('<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"/>'))
|
||||
|
||||
self.wrong_name("schema-element(nil)")
|
||||
self.wrong_name("schema-element(xs:string)")
|
||||
self.check_value("schema-element(xs:complexType)", None)
|
||||
self.check_value("schema-element(xs:schema)", context.item, context)
|
||||
self.check_tree("schema-element(xs:group)", '(schema-element (: (xs) (group)))')
|
||||
|
||||
context.item = AttributeNode(XML_LANG_QNAME, 'en')
|
||||
self.wrong_name("schema-attribute(nil)")
|
||||
self.wrong_name("schema-attribute(xs:string)")
|
||||
self.check_value("schema-attribute(xml:lang)", None)
|
||||
self.check_value("schema-attribute(xml:lang)", context.item, context)
|
||||
self.check_tree("schema-attribute(xsi:schemaLocation)", '(schema-attribute (: (xsi) (schemaLocation)))')
|
||||
|
||||
def test_instance_expression(self):
|
||||
element = self.etree.Element('schema')
|
||||
context = XPathContext(element)
|
||||
|
||||
# Test cases from https://www.w3.org/TR/xpath20/#id-instance-of
|
||||
self.check_value("5 instance of xs:integer", True)
|
||||
self.check_value("5 instance of xs:decimal", True)
|
||||
self.check_value("9.0 instance of xs:integer", False if xmlschema.__version__ >= '1.0.8' else True)
|
||||
self.check_value("(5, 6) instance of xs:integer+", True)
|
||||
self.check_value(". instance of element()", True, context)
|
||||
|
||||
self.check_value("(5, 6) instance of xs:integer", False)
|
||||
self.check_value("(5, 6) instance of xs:integer*", True)
|
||||
self.check_value("(5, 6) instance of xs:integer?", False)
|
||||
|
||||
self.check_value("5 instance of empty-sequence()", False)
|
||||
self.check_value("() instance of empty-sequence()", True)
|
||||
|
||||
def test_treat_expression(self):
|
||||
element = self.etree.Element('schema')
|
||||
context = XPathContext(element)
|
||||
|
||||
self.check_value("5 treat as xs:integer", [5])
|
||||
# self.check_value("5 treat as xs:string", ElementPathTypeError) # FIXME: a bug of xmlschema!
|
||||
self.check_value("5 treat as xs:decimal", [5])
|
||||
self.check_value("(5, 6) treat as xs:integer+", [5, 6])
|
||||
self.check_value(". treat as element()", [element], context)
|
||||
|
||||
self.check_value("(5, 6) treat as xs:integer", ElementPathTypeError)
|
||||
self.check_value("(5, 6) treat as xs:integer*", [5, 6])
|
||||
self.check_value("(5, 6) treat as xs:integer?", ElementPathTypeError)
|
||||
|
||||
self.check_value("5 treat as empty-sequence()", ElementPathTypeError)
|
||||
self.check_value("() treat as empty-sequence()", [])
|
||||
|
||||
def test_castable_expression(self):
|
||||
self.check_value("5 castable as xs:integer", True)
|
||||
self.check_value("'5' castable as xs:integer", True)
|
||||
self.check_value("'hello' castable as xs:integer", False)
|
||||
self.check_value("('5', '6') castable as xs:integer", False)
|
||||
self.check_value("() castable as xs:integer", False)
|
||||
self.check_value("() castable as xs:integer?", True)
|
||||
|
||||
def test_cast_expression(self):
|
||||
self.check_value("5 cast as xs:integer", 5)
|
||||
self.check_value("'5' cast as xs:integer", 5)
|
||||
self.check_value("'hello' cast as xs:integer", ElementPathValueError)
|
||||
self.check_value("('5', '6') cast as xs:integer", ElementPathTypeError)
|
||||
self.check_value("() cast as xs:integer", ElementPathValueError)
|
||||
self.check_value("() cast as xs:integer?", [])
|
||||
self.check_value('"1" cast as xs:boolean', True)
|
||||
self.check_value('"0" cast as xs:boolean', False)
|
||||
|
||||
|
||||
@unittest.skipIf(xmlschema is None, "xmlschema library >= v1.0.7 required.")
|
||||
class LxmlXPath2ParserXMLSchemaTest(XPath2ParserXMLSchemaTest):
|
||||
etree = lxml.etree
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
|
@ -0,0 +1,734 @@
|
|||
#!/usr/bin/env 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>
|
||||
#
|
||||
#
|
||||
# Note: Many tests are built using the examples of the XPath standards,
|
||||
# published by W3C under the W3C Document License.
|
||||
#
|
||||
# References:
|
||||
# http://www.w3.org/TR/1999/REC-xpath-19991116/
|
||||
# http://www.w3.org/TR/2010/REC-xpath20-20101214/
|
||||
# http://www.w3.org/TR/2010/REC-xpath-functions-20101214/
|
||||
# https://www.w3.org/Consortium/Legal/2015/doc-license
|
||||
# https://www.w3.org/TR/charmod-norm/
|
||||
#
|
||||
import unittest
|
||||
import sys
|
||||
import io
|
||||
import math
|
||||
import pickle
|
||||
from decimal import Decimal
|
||||
from collections import namedtuple
|
||||
from xml.etree import ElementTree
|
||||
import lxml.etree
|
||||
|
||||
from elementpath import *
|
||||
from elementpath.namespaces import XML_NAMESPACE, XSD_NAMESPACE, XSI_NAMESPACE, XPATH_FUNCTIONS_NAMESPACE
|
||||
|
||||
|
||||
class XPath1ParserTest(unittest.TestCase):
|
||||
namespaces = {
|
||||
'xml': XML_NAMESPACE,
|
||||
'xs': XSD_NAMESPACE,
|
||||
'xsi': XSI_NAMESPACE,
|
||||
'fn': XPATH_FUNCTIONS_NAMESPACE,
|
||||
'eg': 'http://www.example.com/example',
|
||||
}
|
||||
variables = {'values': [10, 20, 5]}
|
||||
etree = ElementTree
|
||||
|
||||
def setUp(self):
|
||||
self.parser = XPath1Parser(namespaces=self.namespaces, variables=self.variables, strict=True)
|
||||
self.token = XPath1Parser.symbol_table['(name)'](self.parser, 'test')
|
||||
|
||||
#
|
||||
# Helper methods
|
||||
def check_tokenizer(self, path, expected):
|
||||
"""
|
||||
Checks the list of lexemes generated by the parser tokenizer.
|
||||
|
||||
:param path: the XPath expression to be checked.
|
||||
:param expected: a list with lexemes generated by the tokenizer.
|
||||
"""
|
||||
self.assertEqual([
|
||||
lit or op or ref or unexpected
|
||||
for lit, op, ref, unexpected in self.parser.__class__.tokenizer.findall(path)
|
||||
], expected)
|
||||
|
||||
def check_token(self, symbol, expected_label=None, expected_str=None, expected_repr=None, value=None):
|
||||
"""
|
||||
Checks a token class of an XPath parser class. The instance of the token is created
|
||||
using the value argument and than is checked against other optional arguments.
|
||||
|
||||
:param symbol: the string that identifies the token class in the parser's symbol table.
|
||||
:param expected_label: the expected label for the token instance.
|
||||
:param expected_str: the expected string conversion of the token instance.
|
||||
:param expected_repr: the expected string representation of the token instance.
|
||||
:param value: the value used to create the token instance.
|
||||
"""
|
||||
token = self.parser.symbol_table[symbol](self.parser, value)
|
||||
self.assertEqual(token.symbol, symbol)
|
||||
if expected_label is not None:
|
||||
self.assertEqual(token.label, expected_label)
|
||||
if expected_str is not None:
|
||||
self.assertEqual(str(token), expected_str)
|
||||
if expected_repr is not None:
|
||||
self.assertEqual(repr(token), expected_repr)
|
||||
|
||||
def check_tree(self, path, expected):
|
||||
"""
|
||||
Checks the tree string representation of a parsed path.
|
||||
|
||||
:param path: an XPath expression.
|
||||
:param expected: the expected result string.
|
||||
"""
|
||||
self.assertEqual(self.parser.parse(path).tree, expected)
|
||||
|
||||
def check_source(self, path, expected):
|
||||
"""
|
||||
Checks the source representation of a parsed path.
|
||||
|
||||
:param path: an XPath expression.
|
||||
:param expected: the expected result string.
|
||||
"""
|
||||
self.assertEqual(self.parser.parse(path).source, expected)
|
||||
|
||||
def check_value(self, path, expected=None, context=None):
|
||||
"""
|
||||
Checks the result of the *evaluate* method with an XPath expression. The evaluation
|
||||
is applied on the root token of the parsed XPath expression.
|
||||
|
||||
:param path: an XPath expression.
|
||||
:param expected: the expected result. Can be a data instance to compare to the result, a type \
|
||||
to be used to check the type of the result, a function that accepts the result as argument and \
|
||||
returns a boolean value, an exception class that is raised by running the evaluate method.
|
||||
:param context: an optional `XPathContext` instance to be passed to evaluate method.
|
||||
"""
|
||||
if context is not None:
|
||||
context = context.copy()
|
||||
root_token = self.parser.parse(path)
|
||||
if isinstance(expected, type) and issubclass(expected, Exception):
|
||||
self.assertRaises(expected, root_token.evaluate, context)
|
||||
elif not callable(expected):
|
||||
self.assertEqual(root_token.evaluate(context), expected)
|
||||
elif isinstance(expected, type):
|
||||
value = root_token.evaluate(context)
|
||||
self.assertTrue(isinstance(value, expected), "%r is not a %r instance." % (value, expected))
|
||||
else:
|
||||
self.assertTrue(expected(root_token.evaluate(context)))
|
||||
|
||||
def check_select(self, path, expected, context=None):
|
||||
"""
|
||||
Checks the materialized result of the *select* method with an XPath expression.
|
||||
The selection is applied on the root token of the parsed XPath expression.
|
||||
|
||||
:param path: an XPath expression.
|
||||
:param expected: the expected result. Can be a data instance to compare to the result, \
|
||||
a function that accepts the result as argument and returns a boolean value, an exception \
|
||||
class that is raised by running the evaluate method.
|
||||
:param context: an optional `XPathContext` instance to be passed to evaluate method. If no \
|
||||
context is provided the method is called with a dummy context.
|
||||
"""
|
||||
if context is None:
|
||||
context = XPathContext(root=self.etree.Element(u'dummy_root'))
|
||||
else:
|
||||
context = context.copy()
|
||||
root_token = self.parser.parse(path)
|
||||
if isinstance(expected, type) and issubclass(expected, Exception):
|
||||
self.assertRaises(expected, root_token.select, context)
|
||||
elif not callable(expected):
|
||||
self.assertEqual(list(root_token.select(context)), expected)
|
||||
else:
|
||||
self.assertTrue(expected(list(root_token.parse(path).select(context))))
|
||||
|
||||
def check_selector(self, path, root, expected, namespaces=None, **kwargs):
|
||||
"""
|
||||
Checks the selector API, namely the *select* function at package level.
|
||||
|
||||
:param path: an XPath expression.
|
||||
:param root: an Element or an ElementTree instance.
|
||||
:param expected: the expected result. Can be a data instance to compare to the result, a type \
|
||||
to be used to check the type of the result, a function that accepts the result as argument and \
|
||||
returns a boolean value, an exception class that is raised by running the evaluate method.
|
||||
:param namespaces: an optional mapping from prefixes to namespace URIs.
|
||||
:param kwargs: other optional arguments for the parser class.
|
||||
"""
|
||||
if isinstance(expected, type) and issubclass(expected, Exception):
|
||||
self.assertRaises(expected, select, root, path, namespaces, self.parser.__class__, **kwargs)
|
||||
else:
|
||||
results = select(root, path, namespaces, self.parser.__class__, **kwargs)
|
||||
if isinstance(expected, set):
|
||||
self.assertEqual(set(results), expected)
|
||||
elif not callable(expected):
|
||||
self.assertEqual(results, expected)
|
||||
elif isinstance(expected, type):
|
||||
self.assertTrue(isinstance(results, expected))
|
||||
else:
|
||||
self.assertTrue(expected(results))
|
||||
|
||||
# Wrong XPath expression checker shortcuts
|
||||
def wrong_syntax(self, path):
|
||||
self.assertRaises(ElementPathSyntaxError, self.parser.parse, path)
|
||||
|
||||
def wrong_value(self, path):
|
||||
self.assertRaises(ElementPathValueError, self.parser.parse, path)
|
||||
|
||||
def wrong_type(self, path):
|
||||
self.assertRaises(ElementPathTypeError, self.parser.parse, path)
|
||||
|
||||
def wrong_name(self, path):
|
||||
self.assertRaises(ElementPathNameError, self.parser.parse, path)
|
||||
|
||||
#
|
||||
# Test methods
|
||||
@unittest.skipIf(sys.version_info < (3,), "Python 2 pickling is not supported.")
|
||||
def test_parser_pickling(self):
|
||||
if getattr(self.parser, 'schema', None) is None:
|
||||
obj = pickle.dumps(self.parser)
|
||||
parser = pickle.loads(obj)
|
||||
obj = pickle.dumps(self.parser.symbol_table)
|
||||
symbol_table = pickle.loads(obj)
|
||||
self.assertEqual(self.parser, parser)
|
||||
self.assertEqual(self.parser.symbol_table, symbol_table)
|
||||
|
||||
def test_xpath_tokenizer(self):
|
||||
# tests from the XPath specification
|
||||
self.check_tokenizer("*", ['*'])
|
||||
self.check_tokenizer("text()", ['text', '(', ')'])
|
||||
self.check_tokenizer("@name", ['@', 'name'])
|
||||
self.check_tokenizer("@*", ['@', '*'])
|
||||
self.check_tokenizer("para[1]", ['para', '[', '1', ']'])
|
||||
self.check_tokenizer("para[last()]", ['para', '[', 'last', '(', ')', ']'])
|
||||
self.check_tokenizer("*/para", ['*', '/', 'para'])
|
||||
self.check_tokenizer("/doc/chapter[5]/section[2]",
|
||||
['/', 'doc', '/', 'chapter', '[', '5', ']', '/', 'section', '[', '2', ']'])
|
||||
self.check_tokenizer("chapter//para", ['chapter', '//', 'para'])
|
||||
self.check_tokenizer("//para", ['//', 'para'])
|
||||
self.check_tokenizer("//olist/item", ['//', 'olist', '/', 'item'])
|
||||
self.check_tokenizer(".", ['.'])
|
||||
self.check_tokenizer(".//para", ['.', '//', 'para'])
|
||||
self.check_tokenizer("..", ['..'])
|
||||
self.check_tokenizer("../@lang", ['..', '/', '@', 'lang'])
|
||||
self.check_tokenizer("chapter[title]", ['chapter', '[', 'title', ']'])
|
||||
self.check_tokenizer("employee[@secretary and @assistant]",
|
||||
['employee', '[', '@', 'secretary', '', 'and', '', '@', 'assistant', ']'])
|
||||
|
||||
# additional tests from Python XML etree test cases
|
||||
self.check_tokenizer("{http://spam}egg", ['{', 'http', ':', '//', 'spam', '}', 'egg'])
|
||||
self.check_tokenizer("./spam.egg", ['.', '/', 'spam.egg'])
|
||||
self.check_tokenizer(".//spam:egg", ['.', '//', 'spam', ':', 'egg'])
|
||||
|
||||
# additional tests
|
||||
self.check_tokenizer("substring-after()", ['substring-after', '(', ')'])
|
||||
self.check_tokenizer("contains('XML','XM')", ['contains', '(', "'XML'", ',', "'XM'", ')'])
|
||||
self.check_tokenizer("concat('XML', true(), 10)",
|
||||
['concat', '(', "'XML'", ',', '', 'true', '(', ')', ',', '', '10', ')'])
|
||||
self.check_tokenizer("concat('a', 'b', 'c')", ['concat', '(', "'a'", ',', '', "'b'", ',', '', "'c'", ')'])
|
||||
self.check_tokenizer("_last()", ['_last', '(', ')'])
|
||||
self.check_tokenizer("last ()", ['last', '', '(', ')'])
|
||||
self.check_tokenizer('child::text()', ['child', '::', 'text', '(', ')'])
|
||||
|
||||
def test_tokens(self):
|
||||
# Literals
|
||||
self.check_token('(string)', 'literal', "'hello' string",
|
||||
"_string_literal_token(value='hello')", 'hello')
|
||||
self.check_token('(integer)', 'literal', "1999 integer",
|
||||
"_integer_literal_token(value=1999)", 1999)
|
||||
self.check_token('(float)', 'literal', "3.1415 float",
|
||||
"_float_literal_token(value=3.1415)", 3.1415)
|
||||
self.check_token('(decimal)', 'literal', "217.35 decimal",
|
||||
"_decimal_literal_token(value=217.35)", 217.35)
|
||||
self.check_token('(name)', 'literal', "'schema' name",
|
||||
"_name_literal_token(value='schema')", 'schema')
|
||||
|
||||
# Axes
|
||||
self.check_token('self', 'axis', "'self' axis", "_self_axis_token()")
|
||||
self.check_token('child', 'axis', "'child' axis", "_child_axis_token()")
|
||||
self.check_token('parent', 'axis', "'parent' axis", "_parent_axis_token()")
|
||||
self.check_token('ancestor', 'axis', "'ancestor' axis", "_ancestor_axis_token()")
|
||||
self.check_token('preceding', 'axis', "'preceding' axis", "_preceding_axis_token()")
|
||||
self.check_token('descendant-or-self', 'axis', "'descendant-or-self' axis")
|
||||
self.check_token('following-sibling', 'axis', "'following-sibling' axis")
|
||||
self.check_token('preceding-sibling', 'axis', "'preceding-sibling' axis")
|
||||
self.check_token('ancestor-or-self', 'axis', "'ancestor-or-self' axis")
|
||||
self.check_token('descendant', 'axis', "'descendant' axis")
|
||||
if self.parser.version == '1.0':
|
||||
self.check_token('attribute', 'axis', "'attribute' axis")
|
||||
self.check_token('following', 'axis', "'following' axis")
|
||||
self.check_token('namespace', 'axis', "'namespace' axis")
|
||||
|
||||
# Functions
|
||||
self.check_token('position', 'function', "'position' function", "_position_function_token()")
|
||||
|
||||
# Operators
|
||||
self.check_token('and', 'operator', "'and' operator", "_and_operator_token()")
|
||||
|
||||
def test_token_tree(self):
|
||||
self.check_tree('child::B1', '(child (B1))')
|
||||
self.check_tree('A/B//C/D', '(/ (// (/ (A) (B)) (C)) (D))')
|
||||
self.check_tree('child::*/child::B1', '(/ (child (*)) (child (B1)))')
|
||||
self.check_tree('attribute::name="Galileo"', "(= (attribute (name)) ('Galileo'))")
|
||||
self.check_tree('1 + 2 * 3', '(+ (1) (* (2) (3)))')
|
||||
self.check_tree('(1 + 2) * 3', '(* (+ (1) (2)) (3))')
|
||||
self.check_tree("false() and true()", '(and (false) (true))')
|
||||
self.check_tree("false() or true()", '(or (false) (true))')
|
||||
self.check_tree("./A/B[C][D]/E", '(/ ([ ([ (/ (/ (.) (A)) (B)) (C)) (D)) (E))')
|
||||
self.check_tree("string(xml:lang)", '(string (: (xml) (lang)))')
|
||||
|
||||
def test_token_source(self):
|
||||
self.check_source(' child ::B1', 'child::B1')
|
||||
self.check_source('false()', 'false()')
|
||||
self.check_source("concat('alpha', 'beta', 'gamma')", "concat('alpha', 'beta', 'gamma')")
|
||||
self.check_source('1 +2 * 3 ', '1 + 2 * 3')
|
||||
self.check_source('(1 + 2) * 3', '(1 + 2) * 3')
|
||||
self.check_source(' xs : string ', 'xs:string')
|
||||
self.check_source('attribute::name="Galileo"', "attribute::name = 'Galileo'")
|
||||
|
||||
def test_wrong_syntax(self):
|
||||
self.wrong_syntax('')
|
||||
self.wrong_syntax(" \n \n )")
|
||||
self.wrong_syntax('child::1')
|
||||
self.wrong_syntax("count(0, 1, 2)")
|
||||
self.wrong_syntax("{}egg")
|
||||
self.wrong_syntax("./*:*")
|
||||
|
||||
# Features tests
|
||||
def test_references(self):
|
||||
namespaces = {'tst': "http://xpath.test/ns"}
|
||||
root = self.etree.XML("""
|
||||
<A xmlns:tst="http://xpath.test/ns">
|
||||
<tst:B1 b1="beta1"/>
|
||||
<tst:B2/>
|
||||
<tst:B3 b2="tst:beta2" b3="beta3"/>
|
||||
</A>""")
|
||||
|
||||
# Prefix references
|
||||
self.check_tree('xs:string', '(: (xs) (string))')
|
||||
self.check_tree('string(xs:unknown)', '(string (: (xs) (unknown)))')
|
||||
|
||||
self.check_value("fn:true()", True)
|
||||
self.check_selector("./tst:B1", root, [root[0]], namespaces=namespaces)
|
||||
self.check_selector("./tst:*", root, root[:], namespaces=namespaces)
|
||||
|
||||
# Namespace wildcard works only for XPath > 1.0
|
||||
if self.parser.version == '1.0':
|
||||
self.check_selector("./*:B2", root, Exception, namespaces=namespaces)
|
||||
else:
|
||||
self.check_selector("./*:B2", root, [root[1]], namespaces=namespaces)
|
||||
|
||||
# QName URI references
|
||||
self.parser.strict = False
|
||||
self.check_tree('{%s}string' % XSD_NAMESPACE, "({ ('http://www.w3.org/2001/XMLSchema') (string))")
|
||||
self.check_tree('string({%s}unknown)' % XSD_NAMESPACE,
|
||||
"(string ({ ('http://www.w3.org/2001/XMLSchema') (unknown)))")
|
||||
self.wrong_syntax("{%s" % XSD_NAMESPACE)
|
||||
|
||||
self.check_value("{%s}true()" % XPATH_FUNCTIONS_NAMESPACE, True)
|
||||
self.parser.strict = True
|
||||
self.wrong_syntax('{%s}string' % XSD_NAMESPACE)
|
||||
|
||||
if not hasattr(self.etree, 'LxmlError') or self.parser.version > '1.0':
|
||||
# Do not test with XPath 1.0 on lxml.
|
||||
self.check_selector("./{http://www.w3.org/2001/04/xmlenc#}EncryptedData", root, [], strict=False)
|
||||
self.check_selector("./{http://xpath.test/ns}B1", root, [root[0]], strict=False)
|
||||
self.check_selector("./{http://xpath.test/ns}*", root, root[:], strict=False)
|
||||
|
||||
def test_node_types(self):
|
||||
document = self.etree.parse(io.StringIO(u'<A/>'))
|
||||
element = self.etree.Element('schema')
|
||||
attribute = 'id', '0212349350'
|
||||
namespace = namedtuple('Namespace', 'prefix uri')('xs', 'http://www.w3.org/2001/XMLSchema')
|
||||
comment = self.etree.Comment('nothing important')
|
||||
pi = self.etree.ProcessingInstruction('action', 'nothing to do')
|
||||
text = u'aldebaran'
|
||||
context = XPathContext(element)
|
||||
self.check_select("node()", [document.getroot()], context=XPathContext(document))
|
||||
self.check_selector("node()", element, [])
|
||||
context.item = attribute
|
||||
self.check_select("self::node()", [attribute], context)
|
||||
context.item = namespace
|
||||
self.check_select("self::node()", [namespace], context)
|
||||
context.item = comment
|
||||
self.check_select("self::node()", [comment], context)
|
||||
self.check_select("self::comment()", [comment], context)
|
||||
context.item = pi
|
||||
self.check_select("self::node()", [pi], context)
|
||||
self.check_select("self::processing-instruction()", [pi], context)
|
||||
context.item = text
|
||||
self.check_select("self::node()", [text], context)
|
||||
self.check_select("text()", [], context) # Selects the children
|
||||
self.check_selector("node()", self.etree.XML('<author>Dickens</author>'), ['Dickens'])
|
||||
self.check_selector("text()", self.etree.XML('<author>Dickens</author>'), ['Dickens'])
|
||||
self.check_selector("//self::text()", self.etree.XML('<author>Dickens</author>'), ['Dickens'])
|
||||
|
||||
def test_node_set_id_function(self):
|
||||
# XPath 1.0 id() function: https://www.w3.org/TR/1999/REC-xpath-19991116/#function-id
|
||||
root = self.etree.XML('<A><B1 xml:id="foo"/><B2/><B3 xml:id="bar"/><B4 xml:id="baz"/></A>')
|
||||
self.check_selector('id("foo")', root, [root[0]])
|
||||
|
||||
def test_node_set_functions(self):
|
||||
root = self.etree.XML('<A><B1><C1/><C2/></B1><B2/><B3><C3/><C4/><C5/></B3></A>')
|
||||
context = XPathContext(root, item=root[1], size=3, position=3)
|
||||
self.check_value("position()", 0)
|
||||
self.check_value("position()", 4, context=context)
|
||||
self.check_value("position()<=2", True)
|
||||
self.check_value("position()<=2", False, context=context)
|
||||
self.check_value("position()=4", True, context=context)
|
||||
self.check_value("position()=3", False, context=context)
|
||||
self.check_value("last()", 0)
|
||||
self.check_value("last()", 3, context=context)
|
||||
self.check_value("last()-1", 2, context=context)
|
||||
|
||||
self.check_selector("name(.)", root, 'A')
|
||||
self.check_selector("name(A)", root, '')
|
||||
self.check_selector("local-name(A)", root, '')
|
||||
self.check_selector("namespace-uri(A)", root, '')
|
||||
self.check_selector("name(B2)", root, 'B2')
|
||||
self.check_selector("local-name(B2)", root, 'B2')
|
||||
self.check_selector("namespace-uri(B2)", root, '')
|
||||
if self.parser.version <= '1.0':
|
||||
self.check_selector("name(*)", root, 'B1')
|
||||
|
||||
root = self.etree.XML('<tst:A xmlns:tst="http://xpath.test/ns"><tst:B1/></tst:A>')
|
||||
self.check_selector("name(.)", root, 'tst:A', namespaces={'tst': "http://xpath.test/ns"})
|
||||
self.check_selector("local-name(.)", root, 'A')
|
||||
self.check_selector("namespace-uri(.)", root, 'http://xpath.test/ns')
|
||||
self.check_selector("name(tst:B1)", root, 'tst:B1', namespaces={'tst': "http://xpath.test/ns"})
|
||||
self.check_selector("name(tst:B1)", root, 'tst:B1', namespaces={'tst': "http://xpath.test/ns", '': ''})
|
||||
|
||||
def test_string_functions(self):
|
||||
self.check_value("string(10.0)", '10.0')
|
||||
self.check_value("contains('XPath','XP')", True)
|
||||
self.check_value("contains('XP','XPath')", False)
|
||||
self.wrong_type("contains('XPath', 20)")
|
||||
self.wrong_syntax("contains('XPath', 'XP', 20)")
|
||||
self.check_value("concat('alpha', 'beta', 'gamma')", 'alphabetagamma')
|
||||
self.wrong_type("concat('alpha', 10, 'gamma')")
|
||||
self.wrong_syntax("concat()")
|
||||
self.check_value("string-length('hello world')", 11)
|
||||
self.check_value("string-length('')", 0)
|
||||
self.check_value("normalize-space(' hello \t world ')", 'hello world')
|
||||
self.check_value("starts-with('Hello World', 'Hello')", True)
|
||||
self.check_value("starts-with('Hello World', 'hello')", False)
|
||||
self.check_value("translate('hello world', 'hw', 'HW')", 'Hello World')
|
||||
self.wrong_value("translate('hello world', 'hwx', 'HW')")
|
||||
self.check_value("substring('Preem Palver', 1)", 'Preem Palver')
|
||||
self.check_value("substring('Preem Palver', 2)", 'reem Palver')
|
||||
self.check_value("substring('Preem Palver', 7)", 'Palver')
|
||||
self.check_value("substring('Preem Palver', 1, 5)", 'Preem')
|
||||
self.wrong_type("substring('Preem Palver', 'c', 5)")
|
||||
self.wrong_type("substring('Preem Palver', 1, '5')")
|
||||
self.check_value("substring-before('Wolfgang Amadeus Mozart', 'Wolfgang')", '')
|
||||
self.check_value("substring-before('Wolfgang Amadeus Mozart', 'Amadeus')", 'Wolfgang ')
|
||||
self.wrong_type("substring-before('2017-10-27', 10)")
|
||||
self.check_value("substring-after('Wolfgang Amadeus Mozart', 'Amadeus ')", 'Mozart')
|
||||
self.check_value("substring-after('Wolfgang Amadeus Mozart', 'Mozart')", '')
|
||||
|
||||
root = self.etree.XML('<ups-units>'
|
||||
' <unit><power>40kW</power></unit>'
|
||||
' <unit><power>20kW</power></unit>'
|
||||
' <unit><power>30kW</power><model>XYZ</model></unit>'
|
||||
'</ups-units>')
|
||||
variables = {'ups1': root[0], 'ups2': root[1], 'ups3': root[2]}
|
||||
self.check_selector('string($ups1/power)', root, '40kW', variables=variables)
|
||||
|
||||
def test_boolean_functions(self):
|
||||
self.check_value("true()", True)
|
||||
self.check_value("false()", False)
|
||||
self.check_value("not(false())", True)
|
||||
self.check_value("not(true())", False)
|
||||
self.check_value("boolean(0)", False)
|
||||
self.check_value("boolean(1)", True)
|
||||
self.check_value("boolean(-1)", True)
|
||||
self.check_value("boolean('hello!')", True)
|
||||
self.check_value("boolean(' ')", True)
|
||||
self.check_value("boolean('')", False)
|
||||
self.wrong_syntax("boolean()") # Argument required
|
||||
self.wrong_syntax("boolean(1, 5)") # Too much arguments
|
||||
|
||||
# From https://www.w3.org/TR/1999/REC-xpath-19991116/#section-Boolean-Functions
|
||||
self.check_selector('lang("en")', self.etree.XML('<para xml:lang="en"/>'), True)
|
||||
self.check_selector('lang("en")', self.etree.XML('<div xml:lang="en"><para/></div>'), True)
|
||||
self.check_selector('lang("en")', self.etree.XML('<para xml:lang="EN"/>'), True)
|
||||
self.check_selector('lang("en")', self.etree.XML('<para xml:lang="en-us"/>'), True)
|
||||
self.check_selector('lang("en")', self.etree.XML('<para xml:lang="it"/>'), False)
|
||||
|
||||
def test_logical_expressions(self):
|
||||
self.check_value("false() and true()", False)
|
||||
self.check_value("false() or true()", True)
|
||||
self.check_value("true() or false()", True)
|
||||
self.check_value("true() and true()", True)
|
||||
self.check_value("1 and 0", False)
|
||||
self.check_value("1 and 1", True)
|
||||
self.check_value("1 and 'jupiter'", True)
|
||||
self.check_value("0 and 'mars'", False)
|
||||
self.check_value("1 and mars", False)
|
||||
|
||||
def test_comparison_operators(self):
|
||||
self.check_value("0.05 = 0.05", True)
|
||||
self.check_value("19.03 != 19.02999", True)
|
||||
self.check_value("-1.0 = 1.0", False)
|
||||
self.check_value("1 <= 2", True)
|
||||
self.check_value("5 >= 9", False)
|
||||
self.check_value("5 > 3", True)
|
||||
self.check_value("5 < 20.0", True)
|
||||
self.check_value("false() = 1", False)
|
||||
self.check_value("0 = false()", True)
|
||||
self.check_value("2 * 2 = 4", True)
|
||||
|
||||
root = self.etree.XML('<table>'
|
||||
' <unit id="1"><cost>50</cost></unit>'
|
||||
' <unit id="2"><cost>30</cost></unit>'
|
||||
' <unit id="3"><cost>20</cost></unit>'
|
||||
' <unit id="2"><cost>40</cost></unit>'
|
||||
'</table>')
|
||||
self.check_selector("/table/unit[2]/cost <= /table/unit[1]/cost", root, True)
|
||||
self.check_selector("/table/unit[2]/cost > /table/unit[position()!=2]/cost", root, True)
|
||||
self.check_selector("/table/unit[3]/cost > /table/unit[position()!=3]/cost", root, False)
|
||||
|
||||
self.check_selector(". = 'Dickens'", self.etree.XML('<author>Dickens</author>'), True)
|
||||
|
||||
def test_numerical_expressions(self):
|
||||
self.check_value("9", 9)
|
||||
self.check_value("-3", -3)
|
||||
self.check_value("7.1", Decimal('7.1'))
|
||||
self.check_value("0.45e3", 0.45e3)
|
||||
self.check_value(" 7+5 ", 12)
|
||||
self.check_value("8 - 5", 3)
|
||||
self.check_value("-8 - 5", -13)
|
||||
self.check_value("5 div 2", 2.5)
|
||||
self.check_value("11 mod 3", 2)
|
||||
self.check_value("4.5 mod 1.2", Decimal('0.9'))
|
||||
self.check_value("1.23E2 mod 0.6E1", 3.0E0)
|
||||
self.check_value("-3 * 7", -21)
|
||||
self.check_value("9 - 1 + 6", 14)
|
||||
self.check_value("(5 * 7) + 9", 44)
|
||||
self.check_value("-3 * 7", -21)
|
||||
|
||||
def test_number_functions(self):
|
||||
root = self.etree.XML('<A><B1><C/></B1><B2/><B3><C1/><C2/></B3></A>')
|
||||
|
||||
self.check_value("number(5.0)", 5.0)
|
||||
self.check_value("number('text')", math.isnan)
|
||||
self.check_value("number('-11')", -11)
|
||||
self.check_selector("number(9)", root, 9.0)
|
||||
self.check_value("sum($values)", 35)
|
||||
|
||||
# Test cases taken from https://www.w3.org/TR/xquery-operators/#numeric-value-functions
|
||||
self.check_value("ceiling(10.5)", 11)
|
||||
self.check_value("ceiling(-10.5)", -10)
|
||||
self.check_value("floor(10.5)", 10)
|
||||
self.check_value("floor(-10.5)", -11)
|
||||
self.check_value("round(2.5)", 3)
|
||||
self.check_value("round(2.4999)", 2)
|
||||
self.check_value("round(-2.5)", -2)
|
||||
|
||||
def test_context_variables(self):
|
||||
root = self.etree.XML('<A><B1><C/></B1><B2/><B3><C1/><C2/></B3></A>')
|
||||
context = XPathContext(root, variables={'alpha': 10, 'id': '19273222'})
|
||||
self.check_value("$alpha", None) # Do not raise if the dynamic context is None
|
||||
self.check_value("$alpha", 10, context=context)
|
||||
self.check_value("$beta", ElementPathNameError, context=context)
|
||||
self.check_value("$id", '19273222', context=context)
|
||||
self.wrong_syntax("$id()")
|
||||
|
||||
def test_child_operator(self):
|
||||
root = self.etree.XML('<A><B1><C1/></B1><B2/><B3><C1/><C2/></B3></A>')
|
||||
self.check_selector('/', root, [])
|
||||
self.check_selector('/B1', root, [])
|
||||
self.check_selector('/A1', root, [])
|
||||
self.check_selector('/A', root, [root])
|
||||
self.check_selector('/A/B1', root, [root[0]])
|
||||
self.check_selector('/A/*', root, [root[0], root[1], root[2]])
|
||||
self.check_selector('/*/*', root, [root[0], root[1], root[2]])
|
||||
self.check_selector('/A/B1/C1', root, [root[0][0]])
|
||||
self.check_selector('/A/B1/*', root, [root[0][0]])
|
||||
self.check_selector('/A/B3/*', root, [root[2][0], root[2][1]])
|
||||
self.check_selector('child::*/child::C1', root, [root[0][0], root[2][0]])
|
||||
self.check_selector('/A/child::B3', root, [root[2]])
|
||||
self.check_selector('/A/child::C1', root, [])
|
||||
|
||||
def test_context_item_expression(self):
|
||||
root = self.etree.XML('<A><B1><C/></B1><B2/><B3><C1/><C2/></B3></A>')
|
||||
self.check_selector('.', root, [root])
|
||||
self.check_selector('/././.', root, [])
|
||||
self.check_selector('/A/.', root, [root])
|
||||
self.check_selector('/A/B1/.', root, [root[0]])
|
||||
self.check_selector('/A/B1/././.', root, [root[0]])
|
||||
self.check_selector('1/.', root, ElementPathTypeError)
|
||||
|
||||
def test_self_axis(self):
|
||||
root = self.etree.XML('<A>A text<B1>B1 text</B1><B2/><B3>B3 text</B3></A>')
|
||||
self.check_selector('self::node()', root, [root])
|
||||
self.check_selector('self::text()', root, [])
|
||||
|
||||
def test_child_axis(self):
|
||||
root = self.etree.XML('<A>A text<B1>B1 text</B1><B2/><B3>B3 text</B3></A>')
|
||||
self.check_selector('child::B1', root, [root[0]])
|
||||
self.check_selector('child::A', root, [])
|
||||
self.check_selector('child::text()', root, ['A text'])
|
||||
self.check_selector('child::node()', root, ['A text'] + root[:])
|
||||
self.check_selector('child::*', root, root[:])
|
||||
|
||||
def test_descendant_axis(self):
|
||||
root = self.etree.XML('<A><B1><C/></B1><B2/><B3><C1/><C2/></B3></A>')
|
||||
self.check_selector('descendant::node()', root, [e for e in root.iter()][1:])
|
||||
self.check_selector('/descendant::node()', root, [e for e in root.iter()])
|
||||
|
||||
def test_descendant_or_self_axis(self):
|
||||
root = self.etree.XML('<A><B1><C/></B1><B2/><B3><C/><C1/></B3></A>')
|
||||
self.check_selector('//.', root, [e for e in root.iter()])
|
||||
self.check_selector('/A//.', root, [e for e in root.iter()])
|
||||
self.check_selector('//C1', root, [root[2][1]])
|
||||
self.check_selector('//B2', root, [root[1]])
|
||||
self.check_selector('//C', root, [root[0][0], root[2][0]])
|
||||
self.check_selector('//*', root, [e for e in root.iter()])
|
||||
self.check_selector('descendant-or-self::node()', root, [e for e in root.iter()])
|
||||
self.check_selector('descendant-or-self::node()/.', root, [e for e in root.iter()])
|
||||
|
||||
def test_following_axis(self):
|
||||
root = self.etree.XML('<A><B1><C1/></B1><B2/><B3><C1/><C2/></B3><B4><C1><D1/></C1></B4></A>')
|
||||
self.check_selector('/A/B1/C1/following::*', root, [
|
||||
root[1], root[2], root[2][0], root[2][1], root[3], root[3][0], root[3][0][0]
|
||||
])
|
||||
self.check_selector('/A/B1/following::C1', root, [root[2][0], root[3][0]])
|
||||
|
||||
def test_following_sibling_axis(self):
|
||||
root = self.etree.XML('<A><B1><C1/><C2/><C3/></B1><B2><C1/><C2/><C3/><C4/></B2></A>')
|
||||
self.check_selector('/A/B1/C1/following-sibling::*', root, [root[0][1], root[0][2]])
|
||||
self.check_selector('/A/B2/C1/following-sibling::*', root, [root[1][1], root[1][2], root[1][3]])
|
||||
self.check_selector('/A/B1/C1/following-sibling::C3', root, [root[0][2]])
|
||||
|
||||
def test_attribute_abbreviation_and_axis(self):
|
||||
root = self.etree.XML('<A id="1" a="alpha"><B1 b1="beta1"/><B2/><B3 b2="beta2" b3="beta3"/></A>')
|
||||
self.check_selector('/A/B1/attribute::*', root, ['beta1'])
|
||||
self.check_selector('/A/B1/@*', root, ['beta1'])
|
||||
self.check_selector('/A/B3/attribute::*', root, {'beta2', 'beta3'})
|
||||
self.check_selector('/A/attribute::*', root, {'1', 'alpha'})
|
||||
|
||||
def test_namespace_axis(self):
|
||||
root = self.etree.XML('<A xmlns:tst="http://xpath.test/ns"><tst:B1/></A>')
|
||||
namespaces = list(self.parser.DEFAULT_NAMESPACES.items()) + [('tst', 'http://xpath.test/ns')]
|
||||
self.check_selector('/A/namespace::*', root, expected=set(namespaces), namespaces=namespaces[-1:])
|
||||
|
||||
def test_parent_abbreviation_and_axis(self):
|
||||
root = self.etree.XML('<A><B1><C1/></B1><B2/><B3><C1/><C2/></B3><B4><C3><D1/></C3></B4></A>')
|
||||
self.check_selector('/A/*/C2/..', root, [root[2]])
|
||||
self.check_selector('/A/*/*/..', root, [root[0], root[2], root[3]])
|
||||
self.check_selector('//C2/..', root, [root[2]])
|
||||
self.check_selector('/A/*/C2/parent::node()', root, [root[2]])
|
||||
self.check_selector('/A/*/*/parent::node()', root, [root[0], root[2], root[3]])
|
||||
self.check_selector('//C2/parent::node()', root, [root[2]])
|
||||
|
||||
def test_ancestor_axes(self):
|
||||
root = self.etree.XML('<A><B1><C1/></B1><B2><C1/><D2><E1/><E2/></D2><C2/></B2><B3><C1><D1/></C1></B3></A>')
|
||||
self.check_selector('/A/B3/C1/ancestor::*', root, [root, root[2]])
|
||||
self.check_selector('/A/B4/C1/ancestor::*', root, [])
|
||||
self.check_selector('/A/*/C1/ancestor::*', root, [root, root[0], root[1], root[2]])
|
||||
self.check_selector('/A/*/C1/ancestor::B3', root, [root[2]])
|
||||
self.check_selector('/A/B3/C1/ancestor-or-self::*', root, [root, root[2], root[2][0]])
|
||||
self.check_selector('/A/*/C1/ancestor-or-self::*', root, [
|
||||
root, root[0], root[0][0], root[1], root[1][0], root[2], root[2][0]
|
||||
])
|
||||
|
||||
def test_preceding_axes(self):
|
||||
root = self.etree.XML('<A><B1><C1/><C2/><C3/></B1><B2><C1/><C2/><C3/><C4/></B2></A>')
|
||||
self.check_selector('/A/B1/C2/preceding::*', root, [root[0][0]])
|
||||
self.check_selector('/A/B2/C4/preceding::*', root, [
|
||||
root[0], root[0][0], root[0][1], root[0][2], root[1][0], root[1][1], root[1][2]
|
||||
])
|
||||
self.check_selector('/A/B1/C2/preceding-sibling::*', root, [root[0][0]])
|
||||
self.check_selector('/A/B2/C4/preceding-sibling::*', root, [root[1][0], root[1][1], root[1][2]])
|
||||
self.check_selector('/A/B1/C2/preceding-sibling::C3', root, [])
|
||||
|
||||
def test_default_axis(self):
|
||||
"""Tests about when child:: default axis is applied."""
|
||||
root = self.etree.XML('<root><a id="1">first<b/></a><a id="2">second</a></root>')
|
||||
self.check_selector('/root/a/*', root, [root[0][0]])
|
||||
self.check_selector('/root/a/node()', root, ['first', root[0][0], 'second'])
|
||||
self.check_selector('/root/a/text()', root, ['first', 'second'])
|
||||
self.check_selector('/root/a/attribute::*', root, ['1', '2'])
|
||||
if self.parser.version > '1.0':
|
||||
# Functions are not allowed after path step in XPath 1.0
|
||||
self.check_selector('/root/a/attribute()', root, ['1', '2'])
|
||||
self.check_selector('/root/a/element()', root, [root[0][0]])
|
||||
self.check_selector('/root/a/name()', root, ['a', 'a'])
|
||||
self.check_selector('/root/a/last()', root, [2, 2])
|
||||
self.check_selector('/root/a/position()', root, [1, 2])
|
||||
|
||||
def test_predicate(self):
|
||||
root = self.etree.XML('<A><B1><C1/><C2/><C3/></B1><B2><C1/><C2/><C3/><C4/></B2></A>')
|
||||
self.check_selector('/A/B1[C2]', root, [root[0]])
|
||||
self.check_selector('/A/B1[1]', root, [root[0]])
|
||||
self.check_selector('/A/B1[2]', root, [])
|
||||
self.check_selector('/A/*[2]', root, [root[1]])
|
||||
self.check_selector('/A/*[position()<2]', root, [root[0]])
|
||||
self.check_selector('/A/*[last()-1]', root, [root[0]])
|
||||
self.check_selector('/A/B2/*[position()>=2]', root, root[1][1:])
|
||||
|
||||
root = self.etree.XML("<bib><book><author>Asimov</author></book></bib>")
|
||||
self.check_selector("book/author[. = 'Asimov']", root, [root[0][0]])
|
||||
self.check_selector("book/author[. = 'Dickens']", root, [])
|
||||
self.check_selector("book/author[text()='Asimov']", root, [root[0][0]])
|
||||
|
||||
root = self.etree.XML('<A><B1>hello</B1><B2/><B3> </B3></A>')
|
||||
self.check_selector("/A/*[' ']", root, root[:])
|
||||
self.check_selector("/A/*['']", root, [])
|
||||
|
||||
def test_union(self):
|
||||
root = self.etree.XML('<A><B1><C1/><C2/><C3/></B1><B2><C1/><C2/><C3/><C4/></B2><B3/></A>')
|
||||
self.check_selector('/A/B2 | /A/B1', root, root[:2])
|
||||
self.check_selector('/A/B2 | /A/*', root, root[:])
|
||||
|
||||
def test_default_namespace(self):
|
||||
root = self.etree.XML('<foo>bar</foo>')
|
||||
self.check_selector('/foo', root, [root])
|
||||
if type(self.parser) is XPath1Parser:
|
||||
# XPath 1.0 ignores the default namespace
|
||||
self.check_selector('/foo', root, [root], namespaces={'': 'ns'}) # foo --> foo
|
||||
else:
|
||||
self.check_selector('/foo', root, [], namespaces={'': 'ns'}) # foo --> {ns}foo
|
||||
self.check_selector('/*:foo', root, [root], namespaces={'': 'ns'}) # foo --> {ns}foo
|
||||
|
||||
root = self.etree.XML('<foo xmlns="ns">bar</foo>')
|
||||
self.check_selector('/foo', root, [])
|
||||
if type(self.parser) is XPath1Parser:
|
||||
self.check_selector('/foo', root, [], namespaces={'': 'ns'})
|
||||
else:
|
||||
self.check_selector('/foo', root, [root], namespaces={'': 'ns'})
|
||||
|
||||
|
||||
class LxmlXPath1ParserTest(XPath1ParserTest):
|
||||
etree = lxml.etree
|
||||
|
||||
def check_selector(self, path, root, expected, namespaces=None, **kwargs):
|
||||
"""Check using the selector API (the *select* function of the package)."""
|
||||
if isinstance(expected, type) and issubclass(expected, Exception):
|
||||
self.assertRaises(expected, select, root, path, namespaces, self.parser.__class__, **kwargs)
|
||||
else:
|
||||
results = select(root, path, namespaces, self.parser.__class__, **kwargs)
|
||||
variables = kwargs.get('variables', {})
|
||||
if isinstance(expected, set):
|
||||
if namespaces and '' not in namespaces:
|
||||
self.assertEqual(set(root.xpath(path, namespaces=namespaces, **variables)), expected)
|
||||
self.assertEqual(set(results), expected)
|
||||
elif not callable(expected):
|
||||
if namespaces and '' not in namespaces:
|
||||
self.assertEqual(root.xpath(path, namespaces=namespaces, **variables), expected)
|
||||
self.assertEqual(results, expected)
|
||||
elif isinstance(expected, type):
|
||||
self.assertTrue(isinstance(results, expected))
|
||||
else:
|
||||
self.assertTrue(expected(results))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue