Add tests for XPathToken helpers and datatypes

This commit is contained in:
Davide Brunato 2019-08-30 06:47:13 +02:00
parent d89b2ad987
commit 910b63603a
12 changed files with 80 additions and 17 deletions

3
.gitignore vendored
View File

@ -7,7 +7,8 @@
.project
.ipynb_checkpoints/
.tox/
.coverage
.coverage*
!.coveragerc
doc/_*
__pycache__/
dist/

View File

@ -2,9 +2,10 @@
CHANGELOG
*********
`v1.2.1`_ (TBD)
===============
`v1.2.1`_ (2019-08-30)
======================
* Hashable XSD datatypes classes
* Fix Duration types comparison
`v1.2.0`_ (2019-08-14)
======================

View File

@ -40,8 +40,8 @@ class XPath1Parser(Parser):
:param namespaces: A dictionary with mapping from namespace prefixes into URIs.
: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, \
like the ElementPath library. Default is `True`.
:param strict: If strict mode is `False` the parser enables parsing of QNames \
in extended format, like the Python's ElementPath library. Default is `True`.
"""
token_base_class = XPathToken
@ -160,11 +160,14 @@ class XPath1Parser(Parser):
while k < min_args:
if self.parser.next_token.symbol == ')':
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),
k += 1
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(',')
while k < max_args:
@ -179,7 +182,7 @@ class XPath1Parser(Parser):
if self.parser.next_token.symbol == ',':
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(')')
return self

View File

@ -637,7 +637,7 @@ def evaluate(self, context=None):
elif self.symbol != 'cast':
return False
else:
self.wrong_value("atomic value is required")
self.wrong_context_type("an atomic value is required")
try:
if namespace != XSD_NAMESPACE:

View File

@ -38,7 +38,9 @@ class XPathContext(object):
def __init__(self, root, item=None, position=0, size=1, axis=None, variables=None,
current_dt=None, timezone=None):
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
if item is not None:
self.item = item

View File

@ -35,6 +35,9 @@ from .tdop_parser import Token
def ordinal(n):
if n in {11, 12, 13}:
return '%dth' % n
least_significant_digit = n % 10
if least_significant_digit == 1:
return '%dst' % n
@ -81,7 +84,7 @@ class XPathToken(Token):
if symbol == '$':
return '$%s variable reference' % (self[0].value if self else '')
elif symbol == ',':
return 'comma operator'
return 'comma operator' if self.parser.version > '1.0' else 'comma symbol'
elif label == 'function':
return '%r function' % symbol
elif label == 'axis':
@ -104,7 +107,7 @@ class XPathToken(Token):
elif symbol == '$':
return u'$%s' % self[0].source
elif symbol == '{':
return u'{%s}%s' % (self.value, self[0].source)
return u'{%s}%s' % (self[0].value, self[1].value)
elif symbol == 'instance':
return u'%s instance of %s' % (self[0].source, ''.join(t.source for t in self[1:]))
elif symbol == 'treat':

View File

@ -6,8 +6,8 @@ publiccodeYmlVersion: '0.2'
name: elementpath
url: 'https://github.com/sissaschool/elementpath'
landingURL: 'https://github.com/sissaschool/elementpath'
releaseDate: '2019-08-14'
softwareVersion: v1.2.0
releaseDate: '2019-08-30'
softwareVersion: v1.2.1
developmentStatus: stable
platforms:
- linux
@ -24,7 +24,7 @@ maintenance:
contacts:
- name: Davide Brunato
email: davide.brunato@sissa.it
affiliation: ' Scuola Internazionale Superiore di Studi Avanzati'
affiliation: 'Scuola Internazionale Superiore di Studi Avanzati'
legal:
license: MIT
mainCopyrightOwner: Scuola Internazionale Superiore di Studi Avanzati

View File

@ -105,6 +105,10 @@ class UntypedAtomicTest(unittest.TestCase):
self.assertEqual(UntypedAtomic(1) % 2, 1)
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):
@ -427,6 +431,10 @@ class DateTimeTypesTest(unittest.TestCase):
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):
@ -582,6 +590,12 @@ class DurationTypesTest(unittest.TestCase):
def test_year_month_duration(self):
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):

View File

@ -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, \
node_base_uri, node_document_uri, node_children, node_is_id, node_is_idrefs, \
node_nilled, node_kind, node_name
from elementpath.xpath_token import ordinal
from elementpath.xpath_helpers import boolean_value
from elementpath.xpath1_parser import XPath1Parser
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):
err = ElementPathError("unknown error")
@ -228,7 +232,20 @@ class NodeHelpersTest(unittest.TestCase):
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):
elem = ElementTree.Element('A')
@ -244,6 +261,13 @@ class CompatibilityHelpersTest(unittest.TestCase):
self.assertFalse(boolean_value(0))
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__':
unittest.main()

View File

@ -300,7 +300,7 @@ class XPath2ParserXMLSchemaTest(test_xpath2_parser.XPath2ParserTest):
self.check_value("'5' cast as xs:integer", 5)
self.check_value("'hello' cast as xs:integer", ValueError)
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('"1" cast as xs:boolean', True)
self.check_value('"0" cast as xs:boolean', False)

View File

@ -323,6 +323,10 @@ class XPath1ParserTest(unittest.TestCase):
self.check_token('(name)', 'literal', "'schema' name",
"_name_literal_token(value='schema')", 'schema')
# Variables
self.check_token('$', 'operator', "$ variable reference",
"_DollarSign_operator_token()")
# Axes
self.check_token('self', 'axis', "'self' axis", "_self_axis_token()")
self.check_token('child', 'axis', "'child' axis", "_child_axis_token()")
@ -344,6 +348,10 @@ class XPath1ParserTest(unittest.TestCase):
# Operators
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):
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(".//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):
self.wrong_syntax('')
self.wrong_syntax(" \n \n )")

View File

@ -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", "*")', "*c*bra")
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.wrong_value('fn:replace("abracadabra", ".*?", "$1")')