Add validate() to XSD type interface
This commit is contained in:
parent
8cc023d077
commit
77afa93a15
|
@ -106,10 +106,20 @@ class AbstractXsdType(AbstractXsdComponent):
|
|||
`False` otherwise.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def validate(self, obj, *args, **kwargs):
|
||||
"""
|
||||
Validates an XML object node using the XSD type. The argument *obj* is an element
|
||||
for complex type nodes or a text value for simple type nodes. Raises a `ValueError`
|
||||
compatible exception (a `ValueError` or a subclass of it) if the argument is not valid.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def decode(self, obj, *args, **kwargs):
|
||||
"""
|
||||
Decodes XML data using the XSD type.
|
||||
Decodes an XML object node using the XSD type. The argument *obj* is an element
|
||||
for complex type nodes or a text value for simple type nodes. Raises a `ValueError`
|
||||
or a `TypeError` compatible exception if the argument it's not valid.
|
||||
"""
|
||||
|
||||
|
||||
|
|
|
@ -242,9 +242,12 @@ def evaluate(self, context=None):
|
|||
return
|
||||
name = self.value
|
||||
if name[0] != '{' and self.parser.default_namespace:
|
||||
name = u'{%s}%s' % (self.parser.default_namespace, name)
|
||||
tag = u'{%s}%s' % (self.parser.default_namespace, name)
|
||||
else:
|
||||
tag = name
|
||||
|
||||
if isinstance(context, XPathSchemaContext):
|
||||
# Bind with the XSD type
|
||||
xsd_type = self.match_xsd_type(context.item, name)
|
||||
if xsd_type is not None:
|
||||
if isinstance(context.item, AttributeNode):
|
||||
|
@ -254,21 +257,26 @@ def evaluate(self, context=None):
|
|||
return context.item
|
||||
|
||||
elif self.xsd_type is None:
|
||||
# Untyped evaluation
|
||||
if is_attribute_node(context.item, name):
|
||||
return context.item[1]
|
||||
elif is_element_node(context.item, name):
|
||||
elif is_element_node(context.item, tag):
|
||||
return context.item
|
||||
else:
|
||||
# XSD typed evaluation
|
||||
try:
|
||||
if is_attribute_node(context.item, name):
|
||||
return self.xsd_type.decode(context.item[1])
|
||||
elif is_element_node(context.item, name):
|
||||
elif is_element_node(context.item, tag):
|
||||
if self.xsd_type.is_simple():
|
||||
return self.xsd_type.decode(context.item)
|
||||
else:
|
||||
return context.item
|
||||
return self.xsd_type.decode(context.item.text)
|
||||
elif self.xsd_type.has_simple_content():
|
||||
self.xsd_type.decode(context.item.text)
|
||||
return context.item
|
||||
|
||||
except (TypeError, ValueError):
|
||||
self.wrong_context_type("Type %r is not appropriate for the context" % (type(context.item)))
|
||||
msg = "Type {!r} is not appropriate for the context item {!r}"
|
||||
self.wrong_context_type(msg.format(self.xsd_type, context.item))
|
||||
|
||||
|
||||
@method('(name)')
|
||||
|
@ -277,9 +285,12 @@ def select(self, context=None):
|
|||
return
|
||||
name = self.value
|
||||
if name[0] != '{' and self.parser.default_namespace:
|
||||
name = u'{%s}%s' % (self.parser.default_namespace, name)
|
||||
tag = u'{%s}%s' % (self.parser.default_namespace, name)
|
||||
else:
|
||||
tag = name
|
||||
|
||||
if isinstance(context, XPathSchemaContext):
|
||||
# Bind with the XSD type
|
||||
for item in context.iter_children_or_self():
|
||||
xsd_type = self.match_xsd_type(item, name)
|
||||
if xsd_type is not None:
|
||||
|
@ -294,20 +305,22 @@ def select(self, context=None):
|
|||
for item in context.iter_children_or_self():
|
||||
if is_attribute_node(item, name):
|
||||
yield item[1]
|
||||
elif is_element_node(item, name):
|
||||
elif is_element_node(item, tag):
|
||||
yield item
|
||||
else:
|
||||
# Typed selection
|
||||
# XSD typed selection
|
||||
for item in context.iter_children_or_self():
|
||||
try:
|
||||
if is_attribute_node(item, name):
|
||||
yield self.xsd_type.decode(item[1])
|
||||
elif is_element_node(item, name):
|
||||
elif is_element_node(item, tag):
|
||||
if self.xsd_type.is_simple():
|
||||
self.xsd_type.validate(item.text)
|
||||
yield self.xsd_type.decode(item.text)
|
||||
elif self.xsd_type.has_simple_content():
|
||||
self.xsd_type.decode(item.text)
|
||||
yield item
|
||||
else:
|
||||
self.xsd_type.validate(item)
|
||||
yield item
|
||||
yield item
|
||||
except (TypeError, ValueError):
|
||||
msg = "Type {!r} does not match sequence type of {!r}"
|
||||
self.wrong_sequence_type(msg.format(self.xsd_type, item))
|
||||
|
@ -754,13 +767,13 @@ def led(self, left):
|
|||
@method('[')
|
||||
def select(self, context=None):
|
||||
if context is not None:
|
||||
for position, _ in enumerate(self[0].select(context), start=1):
|
||||
for position, item in enumerate(self[0].select(context), start=1):
|
||||
predicate = list(self[1].select(context.copy()))
|
||||
if len(predicate) == 1 and isinstance(predicate[0], NumericTypeProxy):
|
||||
if position == predicate[0]:
|
||||
yield context.item
|
||||
yield item
|
||||
elif self.boolean_value(predicate):
|
||||
yield context.item
|
||||
yield item
|
||||
|
||||
|
||||
###
|
||||
|
|
|
@ -214,6 +214,8 @@ def cast(value):
|
|||
def cast(value, tz=None):
|
||||
if isinstance(value, Date10):
|
||||
return value
|
||||
elif isinstance(value, UntypedAtomic):
|
||||
return Date10.fromstring(str(value), tzinfo=tz)
|
||||
return Date10.fromstring(value, tzinfo=tz)
|
||||
|
||||
|
||||
|
@ -419,7 +421,7 @@ def cast_to_boolean(value, context=None):
|
|||
elif isinstance(value, (int, float, decimal.Decimal)):
|
||||
return bool(value)
|
||||
elif isinstance(value, UntypedAtomic):
|
||||
value = string_base_type(value)
|
||||
value = unicode_type(value)
|
||||
elif not isinstance(value, string_base_type):
|
||||
raise xpath_error('FORG0006', 'the argument has an invalid type %r' % type(value))
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ import decimal
|
|||
import math
|
||||
import operator
|
||||
|
||||
from .compat import MutableSequence, urlparse
|
||||
from .compat import MutableSequence, urlparse, unicode_type
|
||||
from .exceptions import ElementPathError, ElementPathKeyError, \
|
||||
ElementPathTypeError, MissingContextError
|
||||
from .namespaces import XSD_NAMESPACE, XPATH_FUNCTIONS_NAMESPACE, \
|
||||
|
@ -639,9 +639,10 @@ def evaluate(self, context=None):
|
|||
else:
|
||||
self.wrong_context_type("an atomic value is required")
|
||||
|
||||
input_value = self.data_value(result[0])
|
||||
try:
|
||||
if namespace != XSD_NAMESPACE:
|
||||
value = self.parser.schema.cast_as(result[0], atomic_type)
|
||||
value = self.parser.schema.cast_as(input_value, atomic_type)
|
||||
else:
|
||||
local_name = atomic_type.split('}')[1]
|
||||
token_class = self.parser.symbol_table.get(local_name)
|
||||
|
@ -649,13 +650,13 @@ def evaluate(self, context=None):
|
|||
self.unknown_atomic_type("atomic type %r not found in the in-scope schema types" % self[1].source)
|
||||
|
||||
if local_name in {'base64Binary', 'hexBinary'}:
|
||||
value = token_class.cast(result[0], self[0].label == 'literal')
|
||||
value = token_class.cast(input_value, self[0].label == 'literal')
|
||||
elif local_name in {'dateTime', 'date', 'gDay', 'gMonth', 'gMonthDay', 'gYear', 'gYearMonth', 'time'}:
|
||||
value = token_class.cast(result[0], tz=None if context is None else context.timezone)
|
||||
value = token_class.cast(input_value, tz=None if context is None else context.timezone)
|
||||
elif local_name == 'QName':
|
||||
value = token_class.cast(result[0], self.parser.namespaces)
|
||||
value = token_class.cast(input_value, self.parser.namespaces)
|
||||
else:
|
||||
value = token_class.cast(result[0])
|
||||
value = token_class.cast(input_value)
|
||||
|
||||
except ElementPathError as err:
|
||||
if self.symbol != 'cast':
|
||||
|
@ -668,7 +669,7 @@ def evaluate(self, context=None):
|
|||
except TypeError as err:
|
||||
if self.symbol != 'cast':
|
||||
return False
|
||||
self.wrong_type(str(err))
|
||||
self.wrong_type(unicode_type(err))
|
||||
except ValueError as err:
|
||||
if self.symbol != 'cast':
|
||||
return False
|
||||
|
|
|
@ -51,7 +51,7 @@ def elem_iter_strings(elem):
|
|||
# Element-like objects are used for representing elements and comments, ElementTree-like objects
|
||||
# for documents. Generic tuples are used for representing attributes and named-tuples for namespaces.
|
||||
###
|
||||
def is_element_node(obj, tag=None):
|
||||
def is_element_node(obj, tag=None, default_namespace=None):
|
||||
"""
|
||||
Returns `True` if the first argument is an element node matching the tag, `False` otherwise.
|
||||
Raises a ValueError if the argument tag has to be used but it's in a wrong format.
|
||||
|
|
|
@ -434,7 +434,20 @@ class XPathToken(Token):
|
|||
elif not is_xpath_node(obj):
|
||||
return obj
|
||||
elif hasattr(obj, 'type'):
|
||||
return self.schema_node_value(obj)
|
||||
return self.schema_node_value(obj) # Schema context
|
||||
elif self.xsd_type is None:
|
||||
return UntypedAtomic(self.string_value(obj))
|
||||
|
||||
# XSD type bound data
|
||||
try:
|
||||
if is_attribute_node(obj):
|
||||
return self.xsd_type.decode(obj[1])
|
||||
elif is_element_node(obj):
|
||||
return self.xsd_type.decode(obj.text)
|
||||
except TypeError as err:
|
||||
self.wrong_type(str(err))
|
||||
except ValueError as err:
|
||||
self.wrong_value(str(err))
|
||||
else:
|
||||
return UntypedAtomic(self.string_value(obj))
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ class PackageTest(unittest.TestCase):
|
|||
cls.get_version = re.compile(r"(?:\bversion|__version__)(?:\s*=\s*)(\'[^\']*\'|\"[^\"]*\")")
|
||||
|
||||
def test_missing_debug_statements(self):
|
||||
message = "\nFound a debug missing statement at line %d or file %r: %r"
|
||||
message = "\nFound a debug missing statement at line %d of file %r: %r"
|
||||
filename = None
|
||||
for line in fileinput.input(glob.glob(os.path.join(self.source_dir, '*.py'))):
|
||||
if fileinput.isfirstline():
|
||||
|
@ -38,7 +38,7 @@ class PackageTest(unittest.TestCase):
|
|||
self.assertIsNone(match, message % (lineno, filename, match.group(0) if match else None))
|
||||
|
||||
def test_version_matching(self):
|
||||
message = "\nFound a different version at line %d or file %r: %r (maybe %r)."
|
||||
message = "\nFound a different version at line %d of file %r: %r (maybe %r)."
|
||||
files = [
|
||||
os.path.join(self.source_dir, '__init__.py'),
|
||||
os.path.join(self.package_dir, 'setup.py'),
|
||||
|
|
|
@ -244,14 +244,26 @@ class XPath2ParserXMLSchemaTest(test_xpath2_parser.XPath2ParserTest):
|
|||
self.assertEqual(token[0][1].xsd_type, schema.types['rangeType'])
|
||||
self.assertEqual(token[1][0].xsd_type, schema.maps.types['{%s}integer' % XSD_NAMESPACE])
|
||||
|
||||
context = XPathContext(
|
||||
root=self.etree.XML('<values xmlns="http://xpath.test/ns"><b min="19"/></values>'))
|
||||
token = parser.parse("//b/@min lt //b/@max")
|
||||
self.assertEqual(token[0][0][0].xsd_type, schema.types['rangeType'])
|
||||
self.assertEqual(token[0][1][0].xsd_type, schema.maps.types['{%s}integer' % XSD_NAMESPACE])
|
||||
self.assertEqual(token[1][0][0].xsd_type, schema.types['rangeType'])
|
||||
self.assertEqual(token[1][1][0].xsd_type, schema.maps.types['{%s}integer' % XSD_NAMESPACE])
|
||||
self.assertIsNone(token.evaluate(context))
|
||||
|
||||
root = self.etree.XML('<values xmlns="http://xpath.test/ns"><b min="19"/></values>')
|
||||
with self.assertRaises(TypeError):
|
||||
token.evaluate(context=XPathContext(root))
|
||||
|
||||
root = self.etree.XML('<values xmlns="http://xpath.test/ns"><b min="19">30</b></values>')
|
||||
self.assertIsNone(token.evaluate(context=XPathContext(root)))
|
||||
|
||||
root = self.etree.XML('<values xmlns="http://xpath.test/ns"><b min="19" max="40">30</b></values>')
|
||||
context = XPathContext(root)
|
||||
self.assertTrue(token.evaluate(context))
|
||||
|
||||
root = self.etree.XML('<values xmlns="http://xpath.test/ns"><b min="19" max="10">30</b></values>')
|
||||
context = XPathContext(root)
|
||||
self.assertFalse(token.evaluate(context))
|
||||
|
||||
def test_instance_of_expression(self):
|
||||
element = self.etree.Element('schema')
|
||||
|
|
|
@ -911,6 +911,16 @@ class XPath1ParserTest(unittest.TestCase):
|
|||
self.check_selector("count(B)", root, 3)
|
||||
self.check_selector("count(.//C)", root, 5)
|
||||
|
||||
root = self.etree.XML('<value max="10" min="0">5</value>')
|
||||
self.check_selector("count(@avg)", root, 0)
|
||||
self.check_selector("count(@max)", root, 1)
|
||||
self.check_selector("count(@min)", root, 1)
|
||||
self.check_selector("count(@min | @max)", root, 2)
|
||||
self.check_selector("count(@min | @avg)", root, 1)
|
||||
self.check_selector("count(@top | @avg)", root, 0)
|
||||
self.check_selector("count(@min | @max) = 1", root, False)
|
||||
self.check_selector("count(@min | @max) = 2", root, True)
|
||||
|
||||
def test_sum_function(self):
|
||||
root = self.etree.XML(XML_DATA_TEST)
|
||||
self.check_value("sum($values)", 35)
|
||||
|
|
Loading…
Reference in New Issue