From 816007117158b9175f765d70a054cf53458060bb Mon Sep 17 00:00:00 2001 From: Davide Brunato Date: Mon, 18 Jan 2021 21:45:55 +0100 Subject: [PATCH] Extend test coverage for XPath 2.0 functions --- elementpath/xpath2/xpath2_functions.py | 48 +++----- tests/test_xpath1_parser.py | 33 +++-- tests/test_xpath2_functions.py | 162 ++++++++++++++++++------- 3 files changed, 159 insertions(+), 84 deletions(-) diff --git a/elementpath/xpath2/xpath2_functions.py b/elementpath/xpath2/xpath2_functions.py index 44d9286b..3e8a2e32 100644 --- a/elementpath/xpath2/xpath2_functions.py +++ b/elementpath/xpath2/xpath2_functions.py @@ -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 @@ -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: @@ -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) @@ -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))) @@ -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) @@ -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): @@ -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 diff --git a/tests/test_xpath1_parser.py b/tests/test_xpath1_parser.py index c46a7bb4..5fc57e27 100644 --- a/tests/test_xpath1_parser.py +++ b/tests/test_xpath1_parser.py @@ -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 @@ -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 @@ -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-') @@ -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) @@ -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) @@ -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) diff --git a/tests/test_xpath2_functions.py b/tests/test_xpath2_functions.py index d1650cf8..6ffd688e 100644 --- a/tests/test_xpath2_functions.py +++ b/tests/test_xpath2_functions.py @@ -43,9 +43,9 @@ xmlschema.XMLSchema.meta_schema.build() from elementpath import * -from elementpath.namespaces import XSI_NAMESPACE, XML_NAMESPACE -from elementpath.datatypes import DateTime10, DateTime, Date, Time, Timezone, \ - DayTimeDuration, YearMonthDuration, QName, UntypedAtomic +from elementpath.namespaces import XSI_NAMESPACE, XML_NAMESPACE, XML_ID +from elementpath.datatypes import DateTime10, DateTime, Date10, Date, Time, \ + Timezone, DayTimeDuration, YearMonthDuration, QName, UntypedAtomic from elementpath.xpath_token import UNICODE_CODEPOINT_COLLATION try: @@ -221,10 +221,17 @@ def test_min_function(self): # Functions on strings def test_codepoints_to_string_function(self): self.check_value("codepoints-to-string((2309, 2358, 2378, 2325))", 'अशॊक') + self.check_value("codepoints-to-string(2309)", 'अ') + self.wrong_value("codepoints-to-string((55296))", 'FOCH0001') + self.wrong_type("codepoints-to-string(('z'))", 'XPTY0004') + self.wrong_type("codepoints-to-string((2309.1))", 'FORG0006') def test_string_to_codepoints_function(self): self.check_value('string-to-codepoints("Thérèse")', [84, 104, 233, 114, 232, 115, 101]) self.check_value('string-to-codepoints(())') + self.wrong_type('string-to-codepoints(84)', 'XPTY0004') + self.check_value('string-to-codepoints(("Thérèse"))', [84, 104, 233, 114, 232, 115, 101]) + self.wrong_type('string-to-codepoints(("Thér", "èse"))', 'XPTY0004') def test_codepoint_equal_function(self): self.check_value("fn:codepoint-equal('abc', 'abc')", True) @@ -300,7 +307,8 @@ def test_compare_function(self): self.check_value("fn:compare('Strasse', 'Straße', 'invalid_collation')") self.assertIn('FOCH0002', str(cm.exception)) - self.wrong_type("fn:compare('Strasse', 111)") + self.wrong_type("fn:compare('Strasse', 111)", 'XPTY0004') + self.wrong_type('fn:compare("1234", 1234)', 'XPTY0004') finally: locale.setlocale(locale.LC_COLLATE, env_locale_setting) @@ -312,6 +320,7 @@ def test_normalize_unicode_function(self): self.assertRaises(ElementPathValueError, self.parser.parse, 'fn:normalize-unicode("à", "FULLY-NORMALIZED")') + self.check_value('fn:normalize-unicode("à", "")', 'à') self.wrong_value('fn:normalize-unicode("à", "UNKNOWN")') self.wrong_type('fn:normalize-unicode("à", ())', 'XPTY0004', "can't be an empty sequence") @@ -466,6 +475,8 @@ def test_matches_function(self): self.wrong_value('fn:matches("abracadabra", "bra", "k")') self.wrong_value('fn:matches("abracadabra", "[bra")') + self.wrong_value('fn:matches("abracadabra", "a{1,99999999999999999999999999}")', + 'FORX0002') if platform.python_implementation() != 'PyPy' or self.etree is not lxml_etree: poem_context = XPathContext(root=self.etree.XML(XML_POEM_TEST)) @@ -497,10 +508,11 @@ def test_ends_with_function(self): self.check_selector("//b[ends-with(., 't')]", root, [root[0][0]]) self.check_selector("//none[ends-with(., 's')]", root, []) - self.check_value('fn:ends-with ( "tattoo", "tattoo")', True) - self.check_value('fn:ends-with ( "tattoo", "atto")', False) - if self.parser.version > '1.0': - self.check_value("ends-with((), ())", True) + self.check_value('fn:ends-with ( "tattoo", "tattoo", "http://www.w3.org/' + '2005/xpath-functions/collation/codepoint")', True) + self.check_value('fn:ends-with ( "tattoo", "atto", "http://www.w3.org/' + '2005/xpath-functions/collation/codepoint")', False) + self.check_value("ends-with((), ())", True) def test_replace_function(self): self.check_value('fn:replace("abracadabra", "bra", "*")', "a*cada*") @@ -549,17 +561,24 @@ def test_resolve_uri_function(self): self.check_value('fn:resolve-uri("dir1/dir2", "file:///home/")', 'file:///home/dir1/dir2') self.wrong_value('fn:resolve-uri("dir1/dir2", "home/")', '') self.wrong_value('fn:resolve-uri("dir1/dir2")') - context = XPathContext(root=self.etree.XML('')) - parser = XPath2Parser(base_uri='http://www.example.com/ns/') - self.assertEqual( - parser.parse('fn:resolve-uri("dir1/dir2")').evaluate(context), - 'http://www.example.com/ns/dir1/dir2' - ) - self.assertEqual(parser.parse('fn:resolve-uri("/dir1/dir2")').evaluate(context), - '/dir1/dir2') - self.assertEqual(parser.parse('fn:resolve-uri("file:text.txt")').evaluate(context), - 'file:text.txt') - self.assertIsNone(parser.parse('fn:resolve-uri(())').evaluate(context)) + self.check_value('fn:resolve-uri((), "http://xpath.test")') + + self.wrong_value('fn:resolve-uri("file:://file1.txt", "http://xpath.test")', + 'FORG0002', "'file:://file1.txt' is not a valid URI") + self.wrong_value('fn:resolve-uri("dir1/dir2", "http:://xpath.test")', + 'FORG0002', "'http:://xpath.test' is not a valid URI") + + self.parser.base_uri = 'http://www.example.com/ns/' + try: + self.check_value('fn:resolve-uri("dir1/dir2")', 'http://www.example.com/ns/dir1/dir2') + self.check_value('fn:resolve-uri("/dir1/dir2")', '/dir1/dir2') + self.check_value('fn:resolve-uri("file:text.txt")', 'file:text.txt') + self.check_value('fn:resolve-uri(())') + + self.wrong_value('fn:resolve-uri("http:://xpath.test")', + 'FORG0002', "'http:://xpath.test' is not a valid URI") + finally: + self.parser.base_uri = None def test_empty_function(self): # Test cases from https://www.w3.org/TR/xquery-operators/#general-seq-funcs @@ -889,24 +908,25 @@ def test_year_from_datetime_function(self): self.check_value('fn:year-from-dateTime(xs:dateTime("1999-05-31T21:30:00-05:00"))', 1999) self.check_value('fn:year-from-dateTime(xs:dateTime("1999-12-31T19:20:00"))', 1999) self.check_value('fn:year-from-dateTime(xs:dateTime("1999-12-31T24:00:00"))', 2000) + self.check_value('fn:year-from-dateTime(())') def test_month_from_datetime_function(self): self.check_value('fn:month-from-dateTime(xs:dateTime("1999-05-31T13:20:00-05:00"))', 5) self.check_value('fn:month-from-dateTime(xs:dateTime("1999-12-31T19:20:00-05:00"))', 12) - # self.check_value('fn:month-from-dateTime(fn:adjust-dateTime-to-timezone(xs:dateTime(' - # '"1999-12-31T19:20:00-05:00"), xs:dayTimeDuration("PT0S")))', 1) + self.check_value('fn:month-from-dateTime(fn:adjust-dateTime-to-timezone(xs:dateTime(' + '"1999-12-31T19:20:00-05:00"), xs:dayTimeDuration("PT0S")))', 1) def test_day_from_datetime_function(self): self.check_value('fn:day-from-dateTime(xs:dateTime("1999-05-31T13:20:00-05:00"))', 31) self.check_value('fn:day-from-dateTime(xs:dateTime("1999-12-31T20:00:00-05:00"))', 31) - # self.check_value('fn:day-from-dateTime(fn:adjust-dateTime-to-timezone(xs:dateTime(' - # '"1999-12-31T19:20:00-05:00"), xs:dayTimeDuration("PT0S")))', 1) + self.check_value('fn:day-from-dateTime(fn:adjust-dateTime-to-timezone(xs:dateTime(' + '"1999-12-31T19:20:00-05:00"), xs:dayTimeDuration("PT0S")))', 1) def test_hours_from_datetime_function(self): self.check_value('fn:hours-from-dateTime(xs:dateTime("1999-05-31T08:20:00-05:00")) ', 8) self.check_value('fn:hours-from-dateTime(xs:dateTime("1999-12-31T21:20:00-05:00"))', 21) - # self.check_value('fn:hours-from-dateTime(fn:adjust-dateTime-to-timezone(xs:dateTime(' - # '"1999-12-31T21:20:00-05:00"), xs:dayTimeDuration("PT0S")))', 2) + self.check_value('fn:hours-from-dateTime(fn:adjust-dateTime-to-timezone(xs:dateTime(' + '"1999-12-31T21:20:00-05:00"), xs:dayTimeDuration("PT0S")))', 2) self.check_value('fn:hours-from-dateTime(xs:dateTime("1999-12-31T12:00:00")) ', 12) self.check_value('fn:hours-from-dateTime(xs:dateTime("1999-12-31T24:00:00"))', 0) @@ -916,14 +936,18 @@ def test_minutes_from_datetime_function(self): def test_seconds_from_datetime_function(self): self.check_value('fn:seconds-from-dateTime(xs:dateTime("1999-05-31T13:20:00-05:00"))', 0) + self.check_value('seconds-from-dateTime(xs:dateTime("2001-02-03T08:23:12.43"))', + Decimal('12.43')) def test_timezone_from_datetime_function(self): self.check_value('fn:timezone-from-dateTime(xs:dateTime("1999-05-31T13:20:00-05:00"))', DayTimeDuration(seconds=-18000)) + self.check_value('fn:timezone-from-dateTime(())') def test_year_from_date_function(self): self.check_value('fn:year-from-date(xs:date("1999-05-31"))', 1999) self.check_value('fn:year-from-date(xs:date("2000-01-01+05:00"))', 2000) + self.check_value('year-from-date(())') def test_month_from_date_function(self): self.check_value('fn:month-from-date(xs:date("1999-05-31-05:00"))', 5) @@ -938,6 +962,7 @@ def test_timezone_from_date_function(self): DayTimeDuration.fromstring('-PT5H')) self.check_value('fn:timezone-from-date(xs:date("2000-06-12Z"))', DayTimeDuration.fromstring('PT0H')) + self.check_value('fn:timezone-from-date(xs:date("2000-06-12"))') def test_hours_from_time_function(self): self.check_value('fn:hours-from-time(xs:time("11:23:00"))', 11) @@ -959,6 +984,7 @@ def test_seconds_from_time_function(self): def test_timezone_from_time_function(self): self.check_value('fn:timezone-from-time(xs:time("13:20:00-05:00"))', DayTimeDuration.fromstring('-PT5H')) + self.check_value('timezone-from-time(())') def test_years_from_duration_function(self): self.check_value('fn:years-from-duration(())') @@ -1135,18 +1161,19 @@ def test_node_set_idref_function(self): self.check_selector("idref('ID21256')", root, []) context = XPathContext(doc, variables={'x': 11}) - with self.assertRaises(TypeError) as err: - self.check_value("idref('ID21256', $x)", context=context) - self.assertIn('XPTY0004', str(err.exception)) - - context = XPathContext(doc, item=11, variables={'x': 11}) - with self.assertRaises(TypeError) as err: - self.check_value("idref('ID21256', $x)", context=context) - self.assertIn('XPTY0004', str(err.exception)) + self.wrong_type("idref('ID21256', $x)", 'XPTY0004', context=context) context = XPathContext(doc, item=root, variables={'x': root}) self.check_value("idref('ID21256', $x)", [], context=context) + attribute = AttributeNode(XML_ID, 'ID21256', parent=root[0]) + context = XPathContext(doc, item=root, variables={'x': attribute}) + self.check_value("idref('ID21256', $x)", [], context=context) + + context = XPathContext(root, variables={'x': None}) + context.item = None + self.check_value("idref('ID21256', $x)", [], context=context) + def test_deep_equal_function(self): root = self.etree.XML(""" @@ -1207,6 +1234,24 @@ def test_deep_equal_function(self): self.check_value('deep-equal(3.1, xs:anyURI("http://xpath.test")) ', False) + variables = {'a': [TextNode('alpha')], + 'b': [TextNode('beta')]} + context = XPathContext(root, variables=variables) + self.check_value('deep-equal($a, $a)', True, context=context) + self.check_value('deep-equal($a, $b)', False, context=context) + + variables = {'a': [AttributeNode('a', '10')], + 'b': [AttributeNode('b', '10')]} + context = XPathContext(root, variables=variables) + self.check_value('deep-equal($a, $a)', True, context=context) + self.check_value('deep-equal($a, $b)', False, context=context) + + variables = {'a': [NamespaceNode('tns0', 'http://xpath.test/ns')], + 'b': [NamespaceNode('tns1', 'http://xpath.test/ns')]} + context = XPathContext(root, variables=variables) + self.check_value('deep-equal($a, $a)', True, context=context) + self.check_value('deep-equal($a, $b)', False, context=context) + def test_adjust_datetime_to_timezone_function(self): context = XPathContext(root=self.etree.XML(''), timezone=Timezone.fromstring('-05:00'), variables={'tz': DayTimeDuration.fromstring("-PT10H")}) @@ -1286,21 +1331,30 @@ def test_default_collation_function(self): default_collation = self.parser.default_collation self.check_value('fn:default-collation()', default_collation) - def test_context_functions(self): + def test_context_datetime_functions(self): context = XPathContext(root=self.etree.XML('')) - self.check_value('fn:current-dateTime()', DateTime.fromdatetime(context.current_dt), - context=context) + self.check_value('fn:current-dateTime()', context=context, + expected=DateTime10.fromdatetime(context.current_dt)) self.check_value(path='fn:current-date()', context=context, - expected=Date.fromdatetime(context.current_dt.date()),) + expected=Date10.fromdatetime(context.current_dt.date())) self.check_value(path='fn:current-time()', context=context, - expected=Time.fromdatetime(context.current_dt),) + expected=Time.fromdatetime(context.current_dt)) self.check_value(path='fn:implicit-timezone()', context=context, expected=DayTimeDuration(seconds=time.timezone)) context.timezone = Timezone.fromstring('-05:00') self.check_value(path='fn:implicit-timezone()', context=context, expected=DayTimeDuration.fromstring('-PT5H')) + self.parser._xsd_version = '1.1' + try: + self.check_value('fn:current-dateTime()', context=context, + expected=DateTime.fromdatetime(context.current_dt)) + self.check_value(path='fn:current-date()', context=context, + expected=Date.fromdatetime(context.current_dt.date())) + finally: + self.parser._xsd_version = '1.0' + def test_static_base_uri_function(self): context = XPathContext(root=self.etree.XML('')) self.check_value('fn:static-base-uri()', context=context) @@ -1353,6 +1407,8 @@ def test_doc_functions(self): self.check_value("fn:doc(())", context=context) self.check_value("fn:doc-available(())", False, context=context) + self.wrong_value('fn:doc-available(xs:untypedAtomic("2"))', 'FODC0002', context=context) + self.wrong_type('fn:doc-available(2)', 'XPTY0004', context=context) self.check_value("fn:doc('tns0')", doc, context=context) self.check_value("fn:doc-available('tns0')", True, context=context) @@ -1368,13 +1424,16 @@ def test_doc_functions(self): doc = self.etree.XML("") context = XPathContext(root, documents={'tns0': doc}) - with self.assertRaises(TypeError) as err: - self.check_value("fn:doc('tns0')", context=context) - self.assertIn('XPDY0050', str(err.exception)) + self.wrong_type("fn:doc('tns0')", 'XPDY0050', context=context) + self.wrong_type("fn:doc-available('tns0')", 'XPDY0050', context=context) - with self.assertRaises(TypeError) as err: - self.check_value("fn:doc-available('tns0')", context=context) - self.assertIn('XPDY0050', str(err.exception)) + context = XPathContext(root, documents={'file.xml': None}) + self.wrong_value("fn:doc('file.xml')", 'FODC0002', context=context) + self.wrong_value("fn:doc('unknown')", 'FODC0002', context=context) + self.check_value("fn:doc-available('unknown')", False, context=context) + + dirpath = os.path.dirname(__file__) + self.wrong_value("fn:doc('{}')".format(dirpath), 'FODC0005', context=context) def test_collection_function(self): root = self.etree.XML("") @@ -1396,6 +1455,10 @@ def test_collection_function(self): self.check_value("fn:collection()", TypeError, context=context) self.parser.default_collection_type = 'node()*' + context = XPathContext(root) + self.wrong_value("fn:collection('filepath')", 'FODC0002', context=context) + self.wrong_value("fn:collection('dirpath/')", 'FODC0003', context=context) + def test_root_function(self): root = self.etree.XML("") self.check_value("root()", root, context=XPathContext(root)) @@ -1410,6 +1473,7 @@ def test_root_function(self): self.assertIn('XPTY0004', str(err.exception)) context = XPathContext(root, variables={'elem': root[1]}) + self.check_value("fn:root(())", context=context) self.check_value("fn:root($elem)", root, context=context) doc = self.etree.XML("") @@ -1417,11 +1481,17 @@ def test_root_function(self): context = XPathContext(root, variables={'elem': doc[1]}) self.check_value("fn:root($elem)", context=context) + context = XPathContext(root, variables={'elem': doc[1]}, documents={}) + self.check_value("fn:root($elem)", context=context) + context = XPathContext(root, variables={'elem': doc[1]}, documents={'.': doc}) self.check_value("root($elem)", doc, context=context) doc2 = self.etree.XML("") + context = XPathContext(root, variables={'elem': doc2[1]}, documents={'.': doc}) + self.check_value("root($elem)", context=context) + context = XPathContext(root, variables={'elem': doc2[1]}, documents={'.': doc, 'doc2': doc2}) self.check_value("root($elem)", doc2, context=context) @@ -1461,6 +1531,10 @@ def test_error_function(self): ) self.assertEqual(str(err.exception), '[err:XPST0001] Missing schema') + def test_trace_function(self): + self.check_value('trace((), "trace message")', []) + self.check_value('trace("foo", "trace message")', ['foo']) + @unittest.skipIf(lxml_etree is None, "The lxml library is not installed") class LxmlXPath2FunctionsTest(XPath2FunctionsTest):