Add tests for XPathToken helpers and datatypes
This commit is contained in:
parent
d89b2ad987
commit
910b63603a
|
@ -7,7 +7,8 @@
|
||||||
.project
|
.project
|
||||||
.ipynb_checkpoints/
|
.ipynb_checkpoints/
|
||||||
.tox/
|
.tox/
|
||||||
.coverage
|
.coverage*
|
||||||
|
!.coveragerc
|
||||||
doc/_*
|
doc/_*
|
||||||
__pycache__/
|
__pycache__/
|
||||||
dist/
|
dist/
|
||||||
|
|
|
@ -2,9 +2,10 @@
|
||||||
CHANGELOG
|
CHANGELOG
|
||||||
*********
|
*********
|
||||||
|
|
||||||
`v1.2.1`_ (TBD)
|
`v1.2.1`_ (2019-08-30)
|
||||||
===============
|
======================
|
||||||
* Hashable XSD datatypes classes
|
* Hashable XSD datatypes classes
|
||||||
|
* Fix Duration types comparison
|
||||||
|
|
||||||
`v1.2.0`_ (2019-08-14)
|
`v1.2.0`_ (2019-08-14)
|
||||||
======================
|
======================
|
||||||
|
|
|
@ -40,8 +40,8 @@ class XPath1Parser(Parser):
|
||||||
|
|
||||||
:param namespaces: A dictionary with mapping from namespace prefixes into URIs.
|
:param namespaces: A dictionary with mapping from namespace prefixes into URIs.
|
||||||
:param variables: A dictionary with the static context's in-scope variables.
|
:param variables: A dictionary with the static context's in-scope variables.
|
||||||
:param strict: If strict mode is `False` the parser enables parsing of QNames, \
|
:param strict: If strict mode is `False` the parser enables parsing of QNames \
|
||||||
like the ElementPath library. Default is `True`.
|
in extended format, like the Python's ElementPath library. Default is `True`.
|
||||||
"""
|
"""
|
||||||
token_base_class = XPathToken
|
token_base_class = XPathToken
|
||||||
|
|
||||||
|
@ -160,11 +160,14 @@ class XPath1Parser(Parser):
|
||||||
while k < min_args:
|
while k < min_args:
|
||||||
if self.parser.next_token.symbol == ')':
|
if self.parser.next_token.symbol == ')':
|
||||||
msg = 'Too few arguments: expected at least %s arguments' % min_args
|
msg = 'Too few arguments: expected at least %s arguments' % min_args
|
||||||
self.wrong_nargs(msg[:-1] if min_args == 1 else msg)
|
self.wrong_nargs(msg if min_args > 1 else msg[:-1])
|
||||||
|
|
||||||
self[k:] = self.parser.expression(5),
|
self[k:] = self.parser.expression(5),
|
||||||
k += 1
|
k += 1
|
||||||
if k < min_args:
|
if k < min_args:
|
||||||
|
if self.parser.next_token.symbol == ')':
|
||||||
|
msg = 'Too few arguments: expected at least %s arguments' % min_args
|
||||||
|
self.wrong_nargs(msg if min_args > 1 else msg[:-1])
|
||||||
self.parser.advance(',')
|
self.parser.advance(',')
|
||||||
|
|
||||||
while k < max_args:
|
while k < max_args:
|
||||||
|
@ -179,7 +182,7 @@ class XPath1Parser(Parser):
|
||||||
|
|
||||||
if self.parser.next_token.symbol == ',':
|
if self.parser.next_token.symbol == ',':
|
||||||
msg = 'Too many arguments: expected at most %s arguments' % max_args
|
msg = 'Too many arguments: expected at most %s arguments' % max_args
|
||||||
self.wrong_nargs(msg[:-1] if max_args == 1 else msg)
|
self.wrong_nargs(msg if max_args > 1 else msg[:-1])
|
||||||
|
|
||||||
self.parser.advance(')')
|
self.parser.advance(')')
|
||||||
return self
|
return self
|
||||||
|
|
|
@ -637,7 +637,7 @@ def evaluate(self, context=None):
|
||||||
elif self.symbol != 'cast':
|
elif self.symbol != 'cast':
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
self.wrong_value("atomic value is required")
|
self.wrong_context_type("an atomic value is required")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if namespace != XSD_NAMESPACE:
|
if namespace != XSD_NAMESPACE:
|
||||||
|
|
|
@ -38,7 +38,9 @@ class XPathContext(object):
|
||||||
def __init__(self, root, item=None, position=0, size=1, axis=None, variables=None,
|
def __init__(self, root, item=None, position=0, size=1, axis=None, variables=None,
|
||||||
current_dt=None, timezone=None):
|
current_dt=None, timezone=None):
|
||||||
if not is_element_node(root) and not is_document_node(root):
|
if not is_element_node(root) and not is_document_node(root):
|
||||||
raise ElementPathTypeError("argument 'root' must be an Element: %r" % root)
|
raise ElementPathTypeError(
|
||||||
|
"invalid argument root={!r}, an Element is required.".format(root)
|
||||||
|
)
|
||||||
self._root = root
|
self._root = root
|
||||||
if item is not None:
|
if item is not None:
|
||||||
self.item = item
|
self.item = item
|
||||||
|
|
|
@ -35,6 +35,9 @@ from .tdop_parser import Token
|
||||||
|
|
||||||
|
|
||||||
def ordinal(n):
|
def ordinal(n):
|
||||||
|
if n in {11, 12, 13}:
|
||||||
|
return '%dth' % n
|
||||||
|
|
||||||
least_significant_digit = n % 10
|
least_significant_digit = n % 10
|
||||||
if least_significant_digit == 1:
|
if least_significant_digit == 1:
|
||||||
return '%dst' % n
|
return '%dst' % n
|
||||||
|
@ -81,7 +84,7 @@ class XPathToken(Token):
|
||||||
if symbol == '$':
|
if symbol == '$':
|
||||||
return '$%s variable reference' % (self[0].value if self else '')
|
return '$%s variable reference' % (self[0].value if self else '')
|
||||||
elif symbol == ',':
|
elif symbol == ',':
|
||||||
return 'comma operator'
|
return 'comma operator' if self.parser.version > '1.0' else 'comma symbol'
|
||||||
elif label == 'function':
|
elif label == 'function':
|
||||||
return '%r function' % symbol
|
return '%r function' % symbol
|
||||||
elif label == 'axis':
|
elif label == 'axis':
|
||||||
|
@ -104,7 +107,7 @@ class XPathToken(Token):
|
||||||
elif symbol == '$':
|
elif symbol == '$':
|
||||||
return u'$%s' % self[0].source
|
return u'$%s' % self[0].source
|
||||||
elif symbol == '{':
|
elif symbol == '{':
|
||||||
return u'{%s}%s' % (self.value, self[0].source)
|
return u'{%s}%s' % (self[0].value, self[1].value)
|
||||||
elif symbol == 'instance':
|
elif symbol == 'instance':
|
||||||
return u'%s instance of %s' % (self[0].source, ''.join(t.source for t in self[1:]))
|
return u'%s instance of %s' % (self[0].source, ''.join(t.source for t in self[1:]))
|
||||||
elif symbol == 'treat':
|
elif symbol == 'treat':
|
||||||
|
|
|
@ -6,8 +6,8 @@ publiccodeYmlVersion: '0.2'
|
||||||
name: elementpath
|
name: elementpath
|
||||||
url: 'https://github.com/sissaschool/elementpath'
|
url: 'https://github.com/sissaschool/elementpath'
|
||||||
landingURL: 'https://github.com/sissaschool/elementpath'
|
landingURL: 'https://github.com/sissaschool/elementpath'
|
||||||
releaseDate: '2019-08-14'
|
releaseDate: '2019-08-30'
|
||||||
softwareVersion: v1.2.0
|
softwareVersion: v1.2.1
|
||||||
developmentStatus: stable
|
developmentStatus: stable
|
||||||
platforms:
|
platforms:
|
||||||
- linux
|
- linux
|
||||||
|
@ -24,7 +24,7 @@ maintenance:
|
||||||
contacts:
|
contacts:
|
||||||
- name: Davide Brunato
|
- name: Davide Brunato
|
||||||
email: davide.brunato@sissa.it
|
email: davide.brunato@sissa.it
|
||||||
affiliation: ' Scuola Internazionale Superiore di Studi Avanzati'
|
affiliation: 'Scuola Internazionale Superiore di Studi Avanzati'
|
||||||
legal:
|
legal:
|
||||||
license: MIT
|
license: MIT
|
||||||
mainCopyrightOwner: Scuola Internazionale Superiore di Studi Avanzati
|
mainCopyrightOwner: Scuola Internazionale Superiore di Studi Avanzati
|
||||||
|
|
|
@ -105,6 +105,10 @@ class UntypedAtomicTest(unittest.TestCase):
|
||||||
self.assertEqual(UntypedAtomic(1) % 2, 1)
|
self.assertEqual(UntypedAtomic(1) % 2, 1)
|
||||||
self.assertEqual(UntypedAtomic('1') % 2, 1.0)
|
self.assertEqual(UntypedAtomic('1') % 2, 1.0)
|
||||||
|
|
||||||
|
def test_hashing(self):
|
||||||
|
self.assertEqual(hash(UntypedAtomic(12345)), 12345)
|
||||||
|
self.assertIsInstance(hash(UntypedAtomic('alpha')), int)
|
||||||
|
|
||||||
|
|
||||||
class DateTimeTypesTest(unittest.TestCase):
|
class DateTimeTypesTest(unittest.TestCase):
|
||||||
|
|
||||||
|
@ -427,6 +431,10 @@ class DateTimeTypesTest(unittest.TestCase):
|
||||||
|
|
||||||
self.assertEqual(date10("-2001-04-02-02:00") - date10("-2001-04-01"), DayTimeDuration.fromstring('P1DT2H'))
|
self.assertEqual(date10("-2001-04-02-02:00") - date10("-2001-04-01"), DayTimeDuration.fromstring('P1DT2H'))
|
||||||
|
|
||||||
|
def test_hashing(self):
|
||||||
|
dt = DateTime.fromstring("2002-04-02T12:00:00-01:00")
|
||||||
|
self.assertIsInstance(hash(dt), int)
|
||||||
|
|
||||||
|
|
||||||
class DurationTypesTest(unittest.TestCase):
|
class DurationTypesTest(unittest.TestCase):
|
||||||
|
|
||||||
|
@ -582,6 +590,12 @@ class DurationTypesTest(unittest.TestCase):
|
||||||
def test_year_month_duration(self):
|
def test_year_month_duration(self):
|
||||||
self.assertEqual(YearMonthDuration(10).months, 10)
|
self.assertEqual(YearMonthDuration(10).months, 10)
|
||||||
|
|
||||||
|
def test_hashing(self):
|
||||||
|
if sys.version_info < (3, 8):
|
||||||
|
self.assertEqual(hash(Duration(16)), 3713063228956366931)
|
||||||
|
else:
|
||||||
|
self.assertEqual(hash(Duration(16)), 6141449309508620102)
|
||||||
|
|
||||||
|
|
||||||
class TimezoneTypeTest(unittest.TestCase):
|
class TimezoneTypeTest(unittest.TestCase):
|
||||||
|
|
||||||
|
|
|
@ -22,12 +22,16 @@ from elementpath.xpath_nodes import AttributeNode, NamespaceNode, is_etree_eleme
|
||||||
is_namespace_node, is_processing_instruction_node, is_text_node, node_attributes, \
|
is_namespace_node, is_processing_instruction_node, is_text_node, node_attributes, \
|
||||||
node_base_uri, node_document_uri, node_children, node_is_id, node_is_idrefs, \
|
node_base_uri, node_document_uri, node_children, node_is_id, node_is_idrefs, \
|
||||||
node_nilled, node_kind, node_name
|
node_nilled, node_kind, node_name
|
||||||
|
from elementpath.xpath_token import ordinal
|
||||||
from elementpath.xpath_helpers import boolean_value
|
from elementpath.xpath_helpers import boolean_value
|
||||||
from elementpath.xpath1_parser import XPath1Parser
|
from elementpath.xpath1_parser import XPath1Parser
|
||||||
|
|
||||||
|
|
||||||
class ExceptionHelpersTest(unittest.TestCase):
|
class ExceptionHelpersTest(unittest.TestCase):
|
||||||
parser = XPath1Parser(namespaces={'xs': XSD_NAMESPACE, 'tst': "http://xpath.test/ns"})
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
cls.parser = XPath1Parser(namespaces={'xs': XSD_NAMESPACE, 'tst': "http://xpath.test/ns"})
|
||||||
|
|
||||||
def test_exception_repr(self):
|
def test_exception_repr(self):
|
||||||
err = ElementPathError("unknown error")
|
err = ElementPathError("unknown error")
|
||||||
|
@ -228,7 +232,20 @@ class NodeHelpersTest(unittest.TestCase):
|
||||||
self.assertEqual(node_name(namespace), 'xs')
|
self.assertEqual(node_name(namespace), 'xs')
|
||||||
|
|
||||||
|
|
||||||
class CompatibilityHelpersTest(unittest.TestCase):
|
class XPathTokenHelpersTest(unittest.TestCase):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
cls.parser = XPath1Parser(namespaces={'xs': XSD_NAMESPACE, 'tst': "http://xpath.test/ns"})
|
||||||
|
|
||||||
|
def test_ordinal_function(self):
|
||||||
|
self.assertEqual(ordinal(1), '1st')
|
||||||
|
self.assertEqual(ordinal(2), '2nd')
|
||||||
|
self.assertEqual(ordinal(3), '3rd')
|
||||||
|
self.assertEqual(ordinal(4), '4th')
|
||||||
|
self.assertEqual(ordinal(11), '11th')
|
||||||
|
self.assertEqual(ordinal(23), '23rd')
|
||||||
|
self.assertEqual(ordinal(34), '34th')
|
||||||
|
|
||||||
def test_boolean_value_function(self):
|
def test_boolean_value_function(self):
|
||||||
elem = ElementTree.Element('A')
|
elem = ElementTree.Element('A')
|
||||||
|
@ -244,6 +261,13 @@ class CompatibilityHelpersTest(unittest.TestCase):
|
||||||
self.assertFalse(boolean_value(0))
|
self.assertFalse(boolean_value(0))
|
||||||
self.assertTrue(boolean_value(1))
|
self.assertTrue(boolean_value(1))
|
||||||
|
|
||||||
|
def test_get_argument_method(self):
|
||||||
|
token = self.parser.symbol_table['true'](self.parser)
|
||||||
|
|
||||||
|
self.assertIsNone(token.get_argument(2))
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
token.get_argument(1, required=True)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -300,7 +300,7 @@ class XPath2ParserXMLSchemaTest(test_xpath2_parser.XPath2ParserTest):
|
||||||
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", ValueError)
|
self.check_value("'hello' cast as xs:integer", ValueError)
|
||||||
self.check_value("('5', '6') cast as xs:integer", TypeError)
|
self.check_value("('5', '6') cast as xs:integer", TypeError)
|
||||||
self.check_value("() cast as xs:integer", ValueError)
|
self.check_value("() cast as xs:integer", TypeError)
|
||||||
self.check_value("() cast as xs:integer?", [])
|
self.check_value("() cast as xs:integer?", [])
|
||||||
self.check_value('"1" cast as xs:boolean', True)
|
self.check_value('"1" cast as xs:boolean', True)
|
||||||
self.check_value('"0" cast as xs:boolean', False)
|
self.check_value('"0" cast as xs:boolean', False)
|
||||||
|
|
|
@ -323,6 +323,10 @@ class XPath1ParserTest(unittest.TestCase):
|
||||||
self.check_token('(name)', 'literal', "'schema' name",
|
self.check_token('(name)', 'literal', "'schema' name",
|
||||||
"_name_literal_token(value='schema')", 'schema')
|
"_name_literal_token(value='schema')", 'schema')
|
||||||
|
|
||||||
|
# Variables
|
||||||
|
self.check_token('$', 'operator', "$ variable reference",
|
||||||
|
"_DollarSign_operator_token()")
|
||||||
|
|
||||||
# Axes
|
# Axes
|
||||||
self.check_token('self', 'axis', "'self' axis", "_self_axis_token()")
|
self.check_token('self', 'axis', "'self' axis", "_self_axis_token()")
|
||||||
self.check_token('child', 'axis', "'child' axis", "_child_axis_token()")
|
self.check_token('child', 'axis', "'child' axis", "_child_axis_token()")
|
||||||
|
@ -344,6 +348,10 @@ class XPath1ParserTest(unittest.TestCase):
|
||||||
|
|
||||||
# Operators
|
# Operators
|
||||||
self.check_token('and', 'operator', "'and' operator", "_and_operator_token()")
|
self.check_token('and', 'operator', "'and' operator", "_and_operator_token()")
|
||||||
|
if self.parser.version == '1.0':
|
||||||
|
self.check_token(',', 'symbol', "comma symbol", "_Comma_symbol_token()")
|
||||||
|
else:
|
||||||
|
self.check_token(',', 'operator', "comma operator", "_Comma_operator_token()")
|
||||||
|
|
||||||
def test_token_tree(self):
|
def test_token_tree(self):
|
||||||
self.check_tree('child::B1', '(child (B1))')
|
self.check_tree('child::B1', '(child (B1))')
|
||||||
|
@ -367,6 +375,12 @@ class XPath1ParserTest(unittest.TestCase):
|
||||||
self.check_source('attribute::name="Galileo"', "attribute::name = 'Galileo'")
|
self.check_source('attribute::name="Galileo"', "attribute::name = 'Galileo'")
|
||||||
self.check_source(".//eg:a | .//eg:b", '. // eg:a | . // eg:b')
|
self.check_source(".//eg:a | .//eg:b", '. // eg:a | . // eg:b')
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.parser.strict = False
|
||||||
|
self.check_source("{tns1}name", '{tns1}name')
|
||||||
|
finally:
|
||||||
|
self.parser.strict = True
|
||||||
|
|
||||||
def test_wrong_syntax(self):
|
def test_wrong_syntax(self):
|
||||||
self.wrong_syntax('')
|
self.wrong_syntax('')
|
||||||
self.wrong_syntax(" \n \n )")
|
self.wrong_syntax(" \n \n )")
|
||||||
|
|
|
@ -500,6 +500,7 @@ class XPath2ParserTest(test_xpath1_parser.XPath1ParserTest):
|
||||||
self.check_value('fn:replace("abracadabra", "a.*a", "*")', "*")
|
self.check_value('fn:replace("abracadabra", "a.*a", "*")', "*")
|
||||||
self.check_value('fn:replace("abracadabra", "a.*?a", "*")', "*c*bra")
|
self.check_value('fn:replace("abracadabra", "a.*?a", "*")', "*c*bra")
|
||||||
self.check_value('fn:replace("abracadabra", "a", "")', "brcdbr")
|
self.check_value('fn:replace("abracadabra", "a", "")', "brcdbr")
|
||||||
|
self.wrong_type('fn:replace("abracadabra")')
|
||||||
|
|
||||||
self.check_value('fn:replace("abracadabra", "a(.)", "a$1$1")', "abbraccaddabbra")
|
self.check_value('fn:replace("abracadabra", "a(.)", "a$1$1")', "abbraccaddabbra")
|
||||||
self.wrong_value('fn:replace("abracadabra", ".*?", "$1")')
|
self.wrong_value('fn:replace("abracadabra", ".*?", "$1")')
|
||||||
|
|
Loading…
Reference in New Issue