Skip to content

Commit

Permalink
Extend test coverage for XPath 2.0 functions
Browse files Browse the repository at this point in the history
  • Loading branch information
brunato committed Jan 18, 2021
1 parent 8a2bb18 commit 8160071
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 84 deletions.
48 changes: 17 additions & 31 deletions elementpath/xpath2/xpath2_functions.py
Expand Up @@ -21,7 +21,6 @@
from decimal import Decimal, DecimalException
from urllib.parse import urlsplit, quote as urllib_quote

from ..exceptions import ElementPathTypeError
from ..datatypes import QNAME_PATTERN, DateTime10, DateTime, Date10, Date, \
Float10, DoubleProxy, Time, Duration, DayTimeDuration, YearMonthDuration, \
UntypedAtomic, AnyURI, QName, NCName, Id, is_idrefs, ArithmeticProxy, NumericProxy
Expand Down Expand Up @@ -767,17 +766,20 @@ def evaluate(self, context=None):

###
# String functions
def xml10_chr(cp):
if cp in {0x9, 0xA, 0xD} \
or 0x20 <= cp <= 0xD7FF \
or 0xE000 <= cp <= 0xFFFD \
or 0x10000 <= cp <= 0x10FFFF:
return chr(cp)
raise ValueError("{} is not a valid XML 1.0 codepoint".format(cp))


@method(function('codepoints-to-string', nargs=1))
def evaluate(self, context=None):

def xml10_chr(cp: int):
if not isinstance(cp, int):
raise TypeError("invalid type {} for codepoint {}".format(type(cp), cp))
elif cp in {0x9, 0xA, 0xD} \
or 0x20 <= cp <= 0xD7FF \
or 0xE000 <= cp <= 0xFFFD \
or 0x10000 <= cp <= 0x10FFFF:
return chr(cp)
raise ValueError("{} is not a valid XML 1.0 codepoint".format(cp))

try:
return ''.join(xml10_chr(cp) for cp in self[0].select(context))
except TypeError as err:
Expand All @@ -799,14 +801,8 @@ def evaluate(self, context=None):
def evaluate(self, context=None):
comp1 = self.get_argument(context, 0, cls=str, promote=(AnyURI, UntypedAtomic))
comp2 = self.get_argument(context, 1, cls=str, promote=(AnyURI, UntypedAtomic))
if not isinstance(comp1, str):
if comp1 is None:
return
comp1 = str(comp1)
if not isinstance(comp2, str):
if comp2 is None:
return
comp2 = str(comp2)
if comp1 is None or comp2 is None:
return

if len(self) < 3:
value = locale.strcoll(comp1, comp2)
Expand Down Expand Up @@ -844,12 +840,7 @@ def evaluate(self, context=None):
@method(function('string-join', nargs=(1, 2)))
def evaluate(self, context=None):
items = [self.string_value(s) for s in self[0].select(context)]
try:
return self.get_argument(context, 1, required=True, cls=str).join(items)
except ElementPathTypeError:
raise
except TypeError as err:
raise self.error('XPTY0004', "the values must be strings: %s" % err) from None
return self.get_argument(context, 1, required=True, cls=str).join(items)


@method(function('normalize-unicode', nargs=(1, 2)))
Expand Down Expand Up @@ -938,8 +929,6 @@ def evaluate(self, context=None):
def evaluate(self, context=None):
arg1 = self.get_argument(context, default='', cls=str)
arg2 = self.get_argument(context, index=1, default='', cls=str)
if arg1 is None:
return ''

if len(self) < 3:
index = arg1.find(arg2)
Expand Down Expand Up @@ -1231,11 +1220,8 @@ def evaluate(self, context=None):
@method(function('element-with-id', nargs=(1, 2)))
@method(function('id', nargs=(1, 2)))
def select(self, context=None):
try:
idrefs = {x for item in self[0].select(copy(context))
for x in self.string_value(item).split()}
except AttributeError:
raise self.error('XPTY0004', "1st argument must returns strings")
idrefs = {x for item in self[0].select(copy(context))
for x in self.string_value(item).split()}

node = self.get_argument(context, index=1, default_to_context=True)
if isinstance(context, XPathSchemaContext):
Expand Down Expand Up @@ -1419,7 +1405,7 @@ def evaluate(self, context=None):
def select(self, context=None):
label = self.get_argument(context, index=1, cls=str)
for value in self[0].select(context):
'{} {}'.format(label, str(value).strip()) # TODO
'{} {}'.format(label, str(value).strip()) # TODO: trace dataset
yield value


Expand Down
33 changes: 24 additions & 9 deletions tests/test_xpath1_parser.py
Expand Up @@ -689,8 +689,10 @@ def test_starts_with_function(self):
self.wrong_syntax("starts-with((), ())")
self.check_value("starts-with('1999', 19)", True)
else:
self.check_value('fn:starts-with("tattoo", "tat")', True)
self.check_value('fn:starts-with ( "tattoo", "att")', False)
self.check_value('fn:starts-with("tattoo", "tat", "http://www.w3.org/'
'2005/xpath-functions/collation/codepoint")', True)
self.check_value('fn:starts-with ("tattoo", "att", "http://www.w3.org/'
'2005/xpath-functions/collation/codepoint")', False)
self.check_value('fn:starts-with ((), ())', True)
self.wrong_type("starts-with('1999', 19)")
self.parser.compatibility_mode = True
Expand Down Expand Up @@ -741,8 +743,10 @@ def test_contains_function(self):
self.wrong_syntax("contains((), ())")
self.check_value("contains('XPath', 20)", False)
else:
self.check_value('fn:contains ( "tattoo", "t")', True)
self.check_value('fn:contains ( "tattoo", "ttt")', False)
self.check_value('fn:contains ( "tattoo", "t", "http://www.w3.org/'
'2005/xpath-functions/collation/codepoint")', True)
self.check_value('fn:contains ( "tattoo", "ttt", "http://www.w3.org/'
'2005/xpath-functions/collation/codepoint")', False)
self.check_value('fn:contains ( "", ())', True)
self.wrong_type("contains('XPath', 20)")
self.parser.compatibility_mode = True
Expand All @@ -767,9 +771,13 @@ def test_substring_before_function(self):
self.check_value("substring-before('2017-10-27', 10)", '2017-')
self.wrong_syntax("fn:substring-before((), ())")
else:
self.check_value('fn:substring-before ( "tattoo", "attoo")', 't')
self.check_value('fn:substring-before ( "tattoo", "tatto")', '')
self.check_value('fn:substring-before ( "tattoo", "attoo", "http://www.w3.org/'
'2005/xpath-functions/collation/codepoint")', 't')
self.check_value('fn:substring-before ( "tattoo", "tatto", "http://www.w3.org/'
'2005/xpath-functions/collation/codepoint")', '')

self.check_value('fn:substring-before ((), ())', '')
self.check_value('fn:substring-before ((), "")', '')
self.wrong_type("substring-before('2017-10-27', 10)")
self.parser.compatibility_mode = True
self.check_value("substring-before('2017-10-27', 10)", '2017-')
Expand Down Expand Up @@ -873,6 +881,9 @@ def test_lang_function(self):
self.check_selector('lang("en")', root, True)
if self.parser.version > '1.0':
self.check_selector('para/lang("en")', root, True)
context = XPathContext(root)
self.check_value('for $x in . return $x/fn:lang(())',
expected=[], context=context)
else:
context = XPathContext(document, item=root[0])
self.check_value('lang("en")', True, context=context)
Expand All @@ -882,6 +893,7 @@ def test_lang_function(self):
self.check_selector('lang("en")', root, False)
if self.parser.version > '1.0':
self.check_selector('b/c/lang("en")', root, False)
self.check_selector('b/c/lang("en", .)', root, False)
else:
context = XPathContext(root, item=root[0][0])
self.check_value('lang("en")', False, context=context)
Expand All @@ -893,10 +905,13 @@ def test_lang_function(self):

document = self.etree.ElementTree(root)
context = XPathContext(root=document)
if self.parser.version > '1.0':
self.check_value('lang("en")', expected=TypeError, context=context)
else:
if self.parser.version == '1.0':
self.check_value('lang("en")', expected=False, context=context)
else:
self.check_value('lang("en")', expected=TypeError, context=context)
context.item = document
self.check_value('for $x in /a/b/c return $x/fn:lang("en")',
expected=[False], context=context)

def test_logical_and_operator(self):
self.check_value("false() and true()", False)
Expand Down

0 comments on commit 8160071

Please sign in to comment.