Fix function conversion rules in XPathToken.get_argument()
- Added type proxy classes for XSD numeric and datetime data - Added tests for 'mod' operator token
This commit is contained in:
parent
9994139e17
commit
5ae6d0f0ff
|
@ -2,8 +2,12 @@
|
|||
CHANGELOG
|
||||
*********
|
||||
|
||||
`v1.1.9`_ (TBD)
|
||||
`v1.2.0`_ (TBD)
|
||||
===============
|
||||
* Added special XSD datatypes
|
||||
* Better handling of schema contexts
|
||||
* Added validators for numeric types
|
||||
* Fixed function conversion rules
|
||||
* Added tests for uncovered code
|
||||
|
||||
`v1.1.8`_ (2019-05-20)
|
||||
|
|
|
@ -1054,6 +1054,89 @@ LANGUAGE_CODE_PATTERN = re.compile(r'^([a-zA-Z]{2}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{
|
|||
WRONG_ESCAPE_PATTERN = re.compile(r'%(?![a-eA-E\d]{2})')
|
||||
|
||||
|
||||
class TypeProxyMeta(type):
|
||||
"""
|
||||
A metaclass for creating type proxy classes, that can be used for instance
|
||||
and subclass checking and for building instances of related types. A type
|
||||
proxy class has to implement three methods as concrete class/static methods.
|
||||
"""
|
||||
def __instancecheck__(cls, instance):
|
||||
return cls.instance_check(instance)
|
||||
|
||||
def __subclasscheck__(cls, subclass):
|
||||
return cls.subclass_check(subclass)
|
||||
|
||||
def __call__(cls, *args, **kwargs):
|
||||
return cls.instance_build(*args, **kwargs)
|
||||
|
||||
def instance_check(cls, instance):
|
||||
"""Checks the if the argument is an instance of one of related types."""
|
||||
raise NotImplementedError
|
||||
|
||||
def subclass_check(cls, subclass):
|
||||
"""Checks the if the argument is a subclass of one of related types."""
|
||||
raise NotImplementedError
|
||||
|
||||
def instance_build(cls, *args, **kwargs):
|
||||
"""Builds an instance belonging to one of related types."""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@add_metaclass(TypeProxyMeta)
|
||||
class NumericTypeProxy(object):
|
||||
"""
|
||||
A type proxy class for xs:numeric related types (xs:float, xs:decimal and
|
||||
derived types). Builds xs:float instances.
|
||||
"""
|
||||
@staticmethod
|
||||
def instance_check(other):
|
||||
return isinstance(other, (int, float, decimal.Decimal)) and not isinstance(other, bool)
|
||||
|
||||
@staticmethod
|
||||
def subclass_check(subclass):
|
||||
if issubclass(subclass, bool):
|
||||
return False
|
||||
return issubclass(subclass, int) or issubclass(subclass, float) or issubclass(subclass, decimal.Decimal)
|
||||
|
||||
@staticmethod
|
||||
def instance_build(x=0):
|
||||
return float(x)
|
||||
|
||||
|
||||
@add_metaclass(TypeProxyMeta)
|
||||
class ArithmeticTypeProxy(object):
|
||||
"""
|
||||
A type proxy class for XSD types related to arithmetic operators, including
|
||||
types related to xs:numeric and datetime or duration types. Builds xs:float
|
||||
instances.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def instance_check(other):
|
||||
return isinstance(other, (int, float, decimal.Decimal, AbstractDateTime, Duration)) \
|
||||
and not isinstance(other, bool)
|
||||
|
||||
@staticmethod
|
||||
def subclass_check(subclass):
|
||||
if issubclass(subclass, bool):
|
||||
return False
|
||||
return issubclass(subclass, int) or issubclass(subclass, float) or \
|
||||
issubclass(subclass, decimal.Decimal) or issubclass(subclass, Duration) \
|
||||
or issubclass(subclass, AbstractDateTime)
|
||||
|
||||
@staticmethod
|
||||
def instance_build(x=0):
|
||||
return float(x)
|
||||
|
||||
|
||||
def decimal_validator(x):
|
||||
return isinstance(x, (int, decimal.Decimal)) and not isinstance(x, bool)
|
||||
|
||||
|
||||
def integer_validator(x):
|
||||
return isinstance(x, int) and not isinstance(x, bool)
|
||||
|
||||
|
||||
def base64_binary_validator(x):
|
||||
if not isinstance(x, string_base_type) or NOT_BASE64_BINARY_PATTERN.match(x) is None:
|
||||
return False
|
||||
|
@ -1092,7 +1175,7 @@ XSD_BUILTIN_TYPES = {
|
|||
value=' alpha\t'
|
||||
),
|
||||
'decimal': XsdBuiltin(
|
||||
lambda x: isinstance(x, (int, float, decimal.Decimal)) and not isinstance(x, bool),
|
||||
decimal_validator,
|
||||
value=decimal.Decimal('1.0')
|
||||
),
|
||||
'double': XsdBuiltin(
|
||||
|
@ -1208,55 +1291,55 @@ XSD_BUILTIN_TYPES = {
|
|||
value='2000-01-01T12:00:00+01:00'
|
||||
),
|
||||
'integer': XsdBuiltin(
|
||||
lambda x: isinstance(x, int) and not isinstance(x, bool),
|
||||
integer_validator,
|
||||
value=1
|
||||
),
|
||||
'long': XsdBuiltin(
|
||||
lambda x: isinstance(x, int) and not isinstance(x, bool) and (-2**63 <= x < 2**63),
|
||||
lambda x: integer_validator(x) and (-2**63 <= x < 2**63),
|
||||
value=1
|
||||
),
|
||||
'int': XsdBuiltin(
|
||||
lambda x: isinstance(x, int) and not isinstance(x, bool) and (-2**31 <= x < 2**31),
|
||||
lambda x: integer_validator(x) and (-2**31 <= x < 2**31),
|
||||
value=1
|
||||
),
|
||||
'short': XsdBuiltin(
|
||||
lambda x: isinstance(x, int) and not isinstance(x, bool) and (-2**15 <= x < 2**15),
|
||||
lambda x: integer_validator(x) and (-2**15 <= x < 2**15),
|
||||
value=1
|
||||
),
|
||||
'byte': XsdBuiltin(
|
||||
lambda x: isinstance(x, int) and not isinstance(x, bool) and (-2**7 <= x < 2**7),
|
||||
lambda x: integer_validator(x) and (-2**7 <= x < 2**7),
|
||||
value=1
|
||||
),
|
||||
'positiveInteger': XsdBuiltin(
|
||||
lambda x: isinstance(x, int) and not isinstance(x, bool) and x > 0,
|
||||
lambda x: integer_validator(x) and x > 0,
|
||||
value=1
|
||||
),
|
||||
'negativeInteger': XsdBuiltin(
|
||||
lambda x: isinstance(x, int) and not isinstance(x, bool) and x < 0,
|
||||
lambda x: integer_validator(x) and x < 0,
|
||||
value=-1
|
||||
),
|
||||
'nonPositiveInteger': XsdBuiltin(
|
||||
lambda x: isinstance(x, int) and not isinstance(x, bool) and x <= 0,
|
||||
lambda x: integer_validator(x) and x <= 0,
|
||||
value=0
|
||||
),
|
||||
'nonNegativeInteger': XsdBuiltin(
|
||||
lambda x: isinstance(x, int) and not isinstance(x, bool) and x >= 0,
|
||||
lambda x: integer_validator(x) and x >= 0,
|
||||
value=0
|
||||
),
|
||||
'unsignedLong': XsdBuiltin(
|
||||
lambda x: isinstance(x, int) and not isinstance(x, bool) and (0 <= x < 2**64),
|
||||
lambda x: integer_validator(x) and (0 <= x < 2**64),
|
||||
value=1
|
||||
),
|
||||
'unsignedInt': XsdBuiltin(
|
||||
lambda x: isinstance(x, int) and not isinstance(x, bool) and (0 <= x < 2**32),
|
||||
lambda x: integer_validator(x) and (0 <= x < 2**32),
|
||||
value=1
|
||||
),
|
||||
'unsignedShort': XsdBuiltin(
|
||||
lambda x: isinstance(x, int) and not isinstance(x, bool) and (0 <= x < 2**16),
|
||||
lambda x: integer_validator(x) and (0 <= x < 2**16),
|
||||
value=1
|
||||
),
|
||||
'unsignedByte': XsdBuiltin(
|
||||
lambda x: isinstance(x, int) and not isinstance(x, bool) and (0 <= x < 2**8),
|
||||
lambda x: integer_validator(x) and (0 <= x < 2**8),
|
||||
value=1
|
||||
),
|
||||
'boolean': XsdBuiltin(
|
||||
|
|
|
@ -13,9 +13,9 @@ import math
|
|||
import decimal
|
||||
|
||||
from .compat import PY3, string_base_type
|
||||
from .exceptions import ElementPathSyntaxError, ElementPathTypeError, \
|
||||
ElementPathNameError, MissingContextError
|
||||
from .datatypes import UntypedAtomic, DayTimeDuration, YearMonthDuration, XSD_BUILTIN_TYPES
|
||||
from .exceptions import ElementPathSyntaxError, ElementPathNameError, MissingContextError
|
||||
from .datatypes import UntypedAtomic, DayTimeDuration, YearMonthDuration, \
|
||||
NumericTypeProxy, XSD_BUILTIN_TYPES
|
||||
from .xpath_context import XPathSchemaContext
|
||||
from .tdop_parser import Parser, MultiLabel
|
||||
from .namespaces import XML_ID, XML_LANG, XPATH_1_DEFAULT_NAMESPACES, \
|
||||
|
@ -560,15 +560,28 @@ def evaluate(self, context=None):
|
|||
if not self:
|
||||
return
|
||||
elif len(self) == 1:
|
||||
arg = self.get_argument(context, cls=NumericTypeProxy)
|
||||
if arg is None:
|
||||
return
|
||||
try:
|
||||
return +self[0].evaluate(context)
|
||||
return +arg
|
||||
except TypeError:
|
||||
raise ElementPathTypeError("numeric values are required: %r." % self[:])
|
||||
raise self.wrong_type("numeric value is required: %r" % arg)
|
||||
else:
|
||||
arg1 = self.get_argument(context)
|
||||
arg2 = self.get_argument(context, index=1)
|
||||
if arg1 is None or arg2 is None:
|
||||
return
|
||||
elif isinstance(arg1, string_base_type):
|
||||
if isinstance(arg2, string_base_type):
|
||||
raise self.wrong_type("unsupported operands %r and %r" % (arg1, arg2))
|
||||
elif isinstance(arg2, NumericTypeProxy):
|
||||
arg1 = float(arg1)
|
||||
|
||||
try:
|
||||
return self[0].evaluate(context) + self[1].evaluate(context)
|
||||
except TypeError as err:
|
||||
raise ElementPathTypeError(str(err))
|
||||
raise self.wrong_type(str(err))
|
||||
|
||||
|
||||
@method(infix('-', bp=40))
|
||||
|
@ -607,7 +620,13 @@ def evaluate(self, context=None):
|
|||
|
||||
@method(infix('mod', bp=45))
|
||||
def evaluate(self, context=None):
|
||||
return self[0].evaluate(context) % self[1].evaluate(context)
|
||||
arg1 = self.get_argument(context, cls=NumericTypeProxy)
|
||||
arg2 = self.get_argument(context, index=1, cls=NumericTypeProxy)
|
||||
if arg1 is not None and arg2 is not None:
|
||||
try:
|
||||
return arg1 % arg2
|
||||
except TypeError as err:
|
||||
raise self.wrong_type(str(err))
|
||||
|
||||
|
||||
###
|
||||
|
|
|
@ -21,14 +21,13 @@ for documents. Generic tuples are used for representing attributes and named-tup
|
|||
"""
|
||||
import locale
|
||||
import contextlib
|
||||
import decimal
|
||||
|
||||
from .compat import string_base_type
|
||||
from .exceptions import xpath_error
|
||||
from .namespaces import XQT_ERRORS_NAMESPACE
|
||||
from .xpath_nodes import AttributeNode, is_etree_element, \
|
||||
is_element_node, is_document_node, is_xpath_node, node_string_value
|
||||
from .datatypes import UntypedAtomic, Timezone, DayTimeDuration, XSD_BUILTIN_TYPES
|
||||
from .datatypes import UntypedAtomic, Timezone, DayTimeDuration, NumericTypeProxy, XSD_BUILTIN_TYPES
|
||||
from .tdop_parser import Token
|
||||
|
||||
|
||||
|
@ -167,7 +166,7 @@ class XPathToken(Token):
|
|||
if self.parser.compatibility_mode:
|
||||
if issubclass(cls, string_base_type):
|
||||
return self.string_value(item)
|
||||
elif issubclass(cls, float):
|
||||
elif issubclass(cls, float) or cls is NumericTypeProxy:
|
||||
return self.number_value(item)
|
||||
|
||||
if self.parser.version > '1.0':
|
||||
|
@ -176,15 +175,19 @@ class XPathToken(Token):
|
|||
return value
|
||||
elif isinstance(value, UntypedAtomic):
|
||||
try:
|
||||
return str(value) if issubclass(cls, string_base_type) else cls(value)
|
||||
if cls is NumericTypeProxy:
|
||||
return float(value)
|
||||
elif issubclass(cls, string_base_type):
|
||||
return str(value)
|
||||
else:
|
||||
return cls(value)
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
elif issubclass(cls, float) and not isinstance(value, bool) \
|
||||
and isinstance(value, (int, float, decimal.Decimal)):
|
||||
elif issubclass(cls, float) and isinstance(value, NumericTypeProxy):
|
||||
return self.number_value(value)
|
||||
|
||||
code = 'XPTY0004' if self.label == 'function' else 'FORG0006'
|
||||
message = "the %s argument %r is not a %r instance"
|
||||
message = "the %s argument %r is not an instance of %r"
|
||||
raise self.error(code, message % (ordinal(index + 1), item, cls))
|
||||
|
||||
return item
|
||||
|
|
|
@ -16,10 +16,11 @@ import operator
|
|||
import random
|
||||
from decimal import Decimal
|
||||
from calendar import isleap
|
||||
from elementpath.datatypes import MONTH_DAYS, MONTH_DAYS_LEAP, days_from_common_era, months2days, \
|
||||
DateTime, DateTime10, Date, Date10, Time, Timezone, Duration, DayTimeDuration, YearMonthDuration, \
|
||||
UntypedAtomic, GregorianYear, GregorianYear10, GregorianYearMonth, GregorianYearMonth10, \
|
||||
GregorianMonthDay, GregorianMonth, GregorianDay, AbstractDateTime, OrderedDateTime
|
||||
from elementpath.datatypes import MONTH_DAYS, MONTH_DAYS_LEAP, days_from_common_era, \
|
||||
months2days, DateTime, DateTime10, Date, Date10, Time, Timezone, Duration, \
|
||||
DayTimeDuration, YearMonthDuration, UntypedAtomic, GregorianYear, GregorianYear10, \
|
||||
GregorianYearMonth, GregorianYearMonth10, GregorianMonthDay, GregorianMonth, \
|
||||
GregorianDay, AbstractDateTime, OrderedDateTime, NumericTypeProxy, ArithmeticTypeProxy
|
||||
|
||||
|
||||
class UntypedAtomicTest(unittest.TestCase):
|
||||
|
@ -632,5 +633,18 @@ class TimezoneTypeTest(unittest.TestCase):
|
|||
self.assertEqual(hash(Timezone.fromstring('+05:00')), 7009945331308913293)
|
||||
|
||||
|
||||
class TypeProxiesTest(unittest.TestCase):
|
||||
|
||||
def test_numeric_type_proxy(self):
|
||||
self.assertIsInstance(10, NumericTypeProxy)
|
||||
self.assertIsInstance(17.8, NumericTypeProxy)
|
||||
self.assertIsInstance(Decimal('18.12'), NumericTypeProxy)
|
||||
self.assertNotIsInstance(True, NumericTypeProxy)
|
||||
self.assertNotIsInstance(Duration.fromstring('P1Y'), NumericTypeProxy)
|
||||
|
||||
def test_arithmetic_type_proxy(self):
|
||||
self.assertIsInstance(10, ArithmeticTypeProxy)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
|
@ -38,22 +38,25 @@ from elementpath import *
|
|||
from elementpath.namespaces import XML_NAMESPACE, XSD_NAMESPACE, XSI_NAMESPACE, XPATH_FUNCTIONS_NAMESPACE
|
||||
|
||||
|
||||
XML_GENERIC_TEST = """<root>
|
||||
XML_GENERIC_TEST = """
|
||||
<root>
|
||||
<a id="a_id">
|
||||
<b>some content</b>
|
||||
<c> space space \t .</c></a>
|
||||
</root>"""
|
||||
|
||||
XML_DATA_TEST = """<values>
|
||||
XML_DATA_TEST = """
|
||||
<values>
|
||||
<a>3.4</a>
|
||||
<a>20</a>
|
||||
<a>-10.1</a>
|
||||
<b>alpha</b>
|
||||
<c>true</c>
|
||||
</values>
|
||||
"""
|
||||
<d>44</d>
|
||||
</values>"""
|
||||
|
||||
|
||||
# noinspection PyPropertyAccess
|
||||
class XPath1ParserTest(unittest.TestCase):
|
||||
namespaces = {
|
||||
'xml': XML_NAMESPACE,
|
||||
|
@ -466,7 +469,7 @@ class XPath1ParserTest(unittest.TestCase):
|
|||
|
||||
def test_string_function(self):
|
||||
self.check_value("string(10.0)", '10.0')
|
||||
if not isinstance(self.parser, XPath2Parser):
|
||||
if self.parser.version == '1.0':
|
||||
self.wrong_syntax("string(())")
|
||||
else:
|
||||
self.check_value("string(())", '')
|
||||
|
@ -483,7 +486,7 @@ class XPath1ParserTest(unittest.TestCase):
|
|||
self.check_selector("//none[string-length(.) = 10]", root, [])
|
||||
self.check_value('fn:string-length("Harp not on that string, madam; that is past.")', 45)
|
||||
|
||||
if not isinstance(self.parser, XPath2Parser):
|
||||
if self.parser.version == '1.0':
|
||||
self.wrong_syntax("string-length(())")
|
||||
self.check_value("string-length(12345)", 5)
|
||||
else:
|
||||
|
@ -504,7 +507,7 @@ class XPath1ParserTest(unittest.TestCase):
|
|||
self.check_selector("//c[normalize-space(.) = 'space space .']", root, [root[0][1]])
|
||||
self.check_value('fn:normalize-space(" The wealthy curled darlings of our nation. ")',
|
||||
'The wealthy curled darlings of our nation.')
|
||||
if not isinstance(self.parser, XPath2Parser):
|
||||
if self.parser.version == '1.0':
|
||||
self.wrong_syntax('fn:normalize-space(())')
|
||||
self.check_value("normalize-space(1000)", '1000')
|
||||
self.check_value("normalize-space(true())", 'True')
|
||||
|
@ -563,7 +566,7 @@ class XPath1ParserTest(unittest.TestCase):
|
|||
self.check_value("substring('12345', 1.5, 2.6)", '234')
|
||||
self.check_value("substring('12345', 0, 3)", '12')
|
||||
|
||||
if not isinstance(self.parser, XPath2Parser):
|
||||
if self.parser.version == '1.0':
|
||||
self.check_value("substring('12345', 0 div 0, 3)", '')
|
||||
self.check_value("substring('12345', 1, 0 div 0)", '')
|
||||
self.check_value("substring('12345', -42, 1 div 0)", '12345')
|
||||
|
@ -614,7 +617,7 @@ class XPath1ParserTest(unittest.TestCase):
|
|||
self.check_value('fn:starts-with("abracadabra", "a")', True)
|
||||
self.check_value('fn:starts-with("abracadabra", "bra")', False)
|
||||
|
||||
if not isinstance(self.parser, XPath2Parser):
|
||||
if self.parser.version == '1.0':
|
||||
self.wrong_syntax("starts-with((), ())")
|
||||
self.check_value("starts-with('1999', 19)", True)
|
||||
else:
|
||||
|
@ -644,7 +647,7 @@ class XPath1ParserTest(unittest.TestCase):
|
|||
self.wrong_syntax("concat()")
|
||||
|
||||
self.wrong_syntax("concat()")
|
||||
if not isinstance(self.parser, XPath2Parser):
|
||||
if self.parser.version == '1.0':
|
||||
self.wrong_syntax("concat((), (), ())")
|
||||
else:
|
||||
self.check_value("concat((), (), ())", '')
|
||||
|
@ -667,7 +670,7 @@ class XPath1ParserTest(unittest.TestCase):
|
|||
self.check_selector("//b[contains(., ' -con')]", root, [])
|
||||
self.check_selector("//none[contains(., ' -con')]", root, [])
|
||||
|
||||
if not isinstance(self.parser, XPath2Parser):
|
||||
if self.parser.version == '1.0':
|
||||
self.wrong_syntax("contains((), ())")
|
||||
self.check_value("contains('XPath', 20)", False)
|
||||
else:
|
||||
|
@ -693,7 +696,7 @@ class XPath1ParserTest(unittest.TestCase):
|
|||
self.check_selector("//b[substring-before(., 'con') = 'some']", root, [])
|
||||
self.check_selector("//none[substring-before(., 'con') = 'some']", root, [])
|
||||
|
||||
if not isinstance(self.parser, XPath2Parser):
|
||||
if self.parser.version == '1.0':
|
||||
self.check_value("substring-before('2017-10-27', 10)", '2017-')
|
||||
self.wrong_syntax("fn:substring-before((), ())")
|
||||
else:
|
||||
|
@ -724,7 +727,7 @@ class XPath1ParserTest(unittest.TestCase):
|
|||
self.check_selector("//b[substring-after(., 'con') = 'content']", root, [])
|
||||
self.check_selector("//none[substring-after(., 'con') = 'content']", root, [])
|
||||
|
||||
if not isinstance(self.parser, XPath2Parser):
|
||||
if self.parser.version == '1.0':
|
||||
self.wrong_syntax("fn:substring-after((), ())")
|
||||
else:
|
||||
self.check_value('fn:substring-after("tattoo", "tat")', 'too')
|
||||
|
@ -747,7 +750,7 @@ class XPath1ParserTest(unittest.TestCase):
|
|||
self.check_value("boolean(' ')", True)
|
||||
self.check_value("boolean('')", False)
|
||||
|
||||
if not isinstance(self.parser, XPath2Parser):
|
||||
if self.parser.version == '1.0':
|
||||
self.wrong_syntax("boolean(())")
|
||||
else:
|
||||
self.check_value("boolean(())", False)
|
||||
|
@ -804,14 +807,42 @@ class XPath1ParserTest(unittest.TestCase):
|
|||
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_numerical_add_operator(self):
|
||||
self.check_value("3 + 8", 11)
|
||||
self.check_value("9 - 5.0", 4)
|
||||
|
||||
root = self.etree.XML(XML_DATA_TEST)
|
||||
if self.parser.version == '1.0':
|
||||
self.check_value("'9' - 5.0", 4)
|
||||
|
||||
self.check_selector("/values/a mod 2", root, [1.4])
|
||||
self.check_value("/values/b mod 2", float('nan'), context=XPathContext(root))
|
||||
else:
|
||||
self.check_selector("/values/a mod 2", root, TypeError)
|
||||
self.check_value("/values/b mod 2", TypeError, context=XPathContext(root))
|
||||
|
||||
self.check_selector("/values/d mod 3", root, [2])
|
||||
|
||||
def test_numerical_mod_operator(self):
|
||||
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)
|
||||
|
||||
root = self.etree.XML(XML_DATA_TEST)
|
||||
if self.parser.version == '1.0':
|
||||
self.check_selector("/values/a mod 2", root, [1.4])
|
||||
self.check_value("/values/b mod 2", float('nan'), context=XPathContext(root))
|
||||
else:
|
||||
self.check_selector("/values/a mod 2", root, TypeError)
|
||||
self.check_value("/values/b mod 2", TypeError, context=XPathContext(root))
|
||||
|
||||
self.check_selector("/values/d mod 3", root, [2])
|
||||
|
||||
def test_number_function(self):
|
||||
root = self.etree.XML('<root>15</root>')
|
||||
|
||||
|
@ -823,7 +854,7 @@ class XPath1ParserTest(unittest.TestCase):
|
|||
self.check_value("number('-11')", -11)
|
||||
self.check_selector("number(9)", root, 9.0)
|
||||
|
||||
if not isinstance(self.parser, XPath2Parser):
|
||||
if self.parser.version == '1.0':
|
||||
self.wrong_syntax("number(())")
|
||||
else:
|
||||
self.check_value("number(())", float('nan'), context=XPathContext(root))
|
||||
|
@ -837,7 +868,7 @@ class XPath1ParserTest(unittest.TestCase):
|
|||
def test_sum_function(self):
|
||||
root = self.etree.XML(XML_DATA_TEST)
|
||||
self.check_value("sum($values)", 35)
|
||||
if not isinstance(self.parser, XPath2Parser):
|
||||
if self.parser.version == '1.0':
|
||||
self.wrong_syntax("sum(())")
|
||||
else:
|
||||
self.check_value("sum(())", 0)
|
||||
|
@ -851,7 +882,7 @@ class XPath1ParserTest(unittest.TestCase):
|
|||
self.check_value("ceiling(-10.5)", -10)
|
||||
self.check_selector("//a[ceiling(.) = 10]", root, [])
|
||||
self.check_selector("//a[ceiling(.) = -10]", root, [root[2]])
|
||||
if not isinstance(self.parser, XPath2Parser):
|
||||
if self.parser.version == '1.0':
|
||||
self.wrong_syntax("ceiling(())")
|
||||
else:
|
||||
self.check_value("ceiling(())", [])
|
||||
|
@ -865,7 +896,7 @@ class XPath1ParserTest(unittest.TestCase):
|
|||
self.check_selector("//a[floor(.) = 10]", root, [])
|
||||
self.check_selector("//a[floor(.) = 20]", root, [root[1]])
|
||||
|
||||
if not isinstance(self.parser, XPath2Parser):
|
||||
if self.parser.version == '1.0':
|
||||
self.wrong_syntax("floor(())")
|
||||
self.check_selector("//ab[floor(.) = 10]", root, [])
|
||||
else:
|
||||
|
@ -877,7 +908,7 @@ class XPath1ParserTest(unittest.TestCase):
|
|||
self.check_value("round(2.5)", 3)
|
||||
self.check_value("round(2.4999)", 2)
|
||||
self.check_value("round(-2.5)", -2)
|
||||
if not isinstance(self.parser, XPath2Parser):
|
||||
if self.parser.version == '1.0':
|
||||
self.wrong_syntax("round(())")
|
||||
else:
|
||||
self.check_value("round(())", [])
|
||||
|
|
Loading…
Reference in New Issue