Fix assertions
- Add custom parser for assertion facet (without position() and last()). - Move parser initialization to XsdAssert.parse_xpath_test() because all the components must be defined.
This commit is contained in:
parent
b6c6e2ac8f
commit
844ddec3ba
|
@ -2,7 +2,7 @@
|
||||||
setuptools
|
setuptools
|
||||||
tox
|
tox
|
||||||
coverage
|
coverage
|
||||||
elementpath~=1.2.0
|
elementpath~=1.3.0
|
||||||
lxml
|
lxml
|
||||||
memory_profiler
|
memory_profiler
|
||||||
pathlib2 # For Py27 tests on resources
|
pathlib2 # For Py27 tests on resources
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -39,7 +39,7 @@ class InstallCommand(install):
|
||||||
setup(
|
setup(
|
||||||
name='xmlschema',
|
name='xmlschema',
|
||||||
version='1.0.15',
|
version='1.0.15',
|
||||||
install_requires=['elementpath~=1.2.0'],
|
install_requires=['elementpath~=1.3.0'],
|
||||||
packages=['xmlschema'],
|
packages=['xmlschema'],
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
cmdclass={
|
cmdclass={
|
||||||
|
|
4
tox.ini
4
tox.ini
|
@ -11,7 +11,7 @@ toxworkdir = {homedir}/.tox/xmlschema
|
||||||
[testenv]
|
[testenv]
|
||||||
deps =
|
deps =
|
||||||
lxml
|
lxml
|
||||||
elementpath~=1.2.0
|
elementpath~=1.3.0
|
||||||
py27: pathlib2
|
py27: pathlib2
|
||||||
memory: memory_profiler
|
memory: memory_profiler
|
||||||
docs: Sphinx
|
docs: Sphinx
|
||||||
|
@ -25,7 +25,7 @@ whitelist_externals = make
|
||||||
[testenv:py38]
|
[testenv:py38]
|
||||||
deps =
|
deps =
|
||||||
lxml==4.3.5
|
lxml==4.3.5
|
||||||
elementpath~=1.2.0
|
elementpath~=1.3.0
|
||||||
|
|
||||||
[testenv:package]
|
[testenv:package]
|
||||||
commands = python xmlschema/tests/test_package.py
|
commands = python xmlschema/tests/test_package.py
|
||||||
|
|
|
@ -45,43 +45,43 @@ class XsdXPathTest(unittest.TestCase):
|
||||||
self.assertTrue(self.xs1.findall('.'))
|
self.assertTrue(self.xs1.findall('.'))
|
||||||
self.assertTrue(isinstance(self.xs1.find('.'), XMLSchema))
|
self.assertTrue(isinstance(self.xs1.find('.'), XMLSchema))
|
||||||
self.assertTrue(sorted(self.xs1.findall("*"), key=lambda x: x.name) == elements)
|
self.assertTrue(sorted(self.xs1.findall("*"), key=lambda x: x.name) == elements)
|
||||||
self.assertTrue(self.xs1.findall("*") == self.xs1.findall("./*"))
|
self.assertListEqual(self.xs1.findall("*"), self.xs1.findall("./*"))
|
||||||
self.assertTrue(self.xs1.find("./vh:bikes") == self.xs1.elements['bikes'])
|
self.assertEqual(self.xs1.find("./vh:bikes"), self.xs1.elements['bikes'])
|
||||||
self.assertTrue(self.xs1.find("./vh:vehicles/vh:cars").name == self.xs1.elements['cars'].name)
|
self.assertEqual(self.xs1.find("./vh:vehicles/vh:cars").name, self.xs1.elements['cars'].name)
|
||||||
self.assertFalse(self.xs1.find("./vh:vehicles/vh:cars") == self.xs1.elements['cars'])
|
self.assertNotEqual(self.xs1.find("./vh:vehicles/vh:cars"), self.xs1.elements['cars'])
|
||||||
self.assertFalse(self.xs1.find("/vh:vehicles/vh:cars") == self.xs1.elements['cars'])
|
self.assertNotEqual(self.xs1.find("/vh:vehicles/vh:cars"), self.xs1.elements['cars'])
|
||||||
self.assertTrue(self.xs1.find("vh:vehicles/vh:cars/..") == self.xs1.elements['vehicles'])
|
self.assertEqual(self.xs1.find("vh:vehicles/vh:cars/.."), self.xs1.elements['vehicles'])
|
||||||
self.assertTrue(self.xs1.find("vh:vehicles/*/..") == self.xs1.elements['vehicles'])
|
self.assertEqual(self.xs1.find("vh:vehicles/*/.."), self.xs1.elements['vehicles'])
|
||||||
self.assertTrue(self.xs1.find("vh:vehicles/vh:cars/../vh:cars") == self.xs1.find("vh:vehicles/vh:cars"))
|
self.assertEqual(self.xs1.find("vh:vehicles/vh:cars/../vh:cars"), self.xs1.find("vh:vehicles/vh:cars"))
|
||||||
|
|
||||||
def test_xpath_axis(self):
|
def test_xpath_axis(self):
|
||||||
self.assertTrue(self.xs1.find("vh:vehicles/child::vh:cars/..") == self.xs1.elements['vehicles'])
|
self.assertEqual(self.xs1.find("vh:vehicles/child::vh:cars/.."), self.xs1.elements['vehicles'])
|
||||||
|
|
||||||
def test_xpath_subscription(self):
|
def test_xpath_subscription(self):
|
||||||
self.assertTrue(len(self.xs1.findall("./vh:vehicles/*")) == 2)
|
self.assertEqual(len(self.xs1.findall("./vh:vehicles/*")), 2)
|
||||||
self.assertTrue(self.xs1.findall("./vh:vehicles/*[2]") == [self.bikes])
|
self.assertListEqual(self.xs1.findall("./vh:vehicles/*[2]"), [self.bikes])
|
||||||
self.assertTrue(self.xs1.findall("./vh:vehicles/*[3]") == [])
|
self.assertListEqual(self.xs1.findall("./vh:vehicles/*[3]"), [])
|
||||||
self.assertTrue(self.xs1.findall("./vh:vehicles/*[last()-1]") == [self.cars])
|
self.assertListEqual(self.xs1.findall("./vh:vehicles/*[last()-1]"), [self.cars])
|
||||||
self.assertTrue(self.xs1.findall("./vh:vehicles/*[position()=last()]") == [self.bikes])
|
self.assertListEqual(self.xs1.findall("./vh:vehicles/*[position()=last()]"), [self.bikes])
|
||||||
|
|
||||||
def test_xpath_group(self):
|
def test_xpath_group(self):
|
||||||
self.assertTrue(self.xs1.findall("/(vh:vehicles/*/*)") == self.xs1.findall("/vh:vehicles/*/*"))
|
self.assertEqual(self.xs1.findall("/(vh:vehicles/*/*)"), self.xs1.findall("/vh:vehicles/*/*"))
|
||||||
self.assertTrue(self.xs1.findall("/(vh:vehicles/*/*)[1]") == self.xs1.findall("/vh:vehicles/*/*[1]"))
|
self.assertEqual(self.xs1.findall("/(vh:vehicles/*/*)[1]"), self.xs1.findall("/vh:vehicles/*/*[1]")[:1])
|
||||||
|
|
||||||
def test_xpath_predicate(self):
|
def test_xpath_predicate(self):
|
||||||
car = self.xs1.elements['cars'].type.content_type[0]
|
car = self.xs1.elements['cars'].type.content_type[0]
|
||||||
self.assertTrue(self.xs1.findall("./vh:vehicles/vh:cars/vh:car[@make]") == [car])
|
self.assertListEqual(self.xs1.findall("./vh:vehicles/vh:cars/vh:car[@make]"), [car])
|
||||||
self.assertTrue(self.xs1.findall("./vh:vehicles/vh:cars/vh:car[@make]") == [car])
|
self.assertListEqual(self.xs1.findall("./vh:vehicles/vh:cars/vh:car[@make]"), [car])
|
||||||
self.assertTrue(self.xs1.findall("./vh:vehicles/vh:cars['ciao']") == [self.cars])
|
self.assertListEqual(self.xs1.findall("./vh:vehicles/vh:cars['ciao']"), [self.cars])
|
||||||
self.assertTrue(self.xs1.findall("./vh:vehicles/*['']") == [])
|
self.assertListEqual(self.xs1.findall("./vh:vehicles/*['']"), [])
|
||||||
|
|
||||||
def test_xpath_descendants(self):
|
def test_xpath_descendants(self):
|
||||||
selector = Selector('.//xs:element', self.xs2.namespaces, parser=XPath1Parser)
|
selector = Selector('.//xs:element', self.xs2.namespaces, parser=XPath1Parser)
|
||||||
elements = list(selector.iter_select(self.xs2.root))
|
elements = list(selector.iter_select(self.xs2.root))
|
||||||
self.assertTrue(len(elements) == 14)
|
self.assertEqual(len(elements), 14)
|
||||||
selector = Selector('.//xs:element|.//xs:attribute|.//xs:keyref', self.xs2.namespaces, parser=XPath1Parser)
|
selector = Selector('.//xs:element|.//xs:attribute|.//xs:keyref', self.xs2.namespaces, parser=XPath1Parser)
|
||||||
elements = list(selector.iter_select(self.xs2.root))
|
elements = list(selector.iter_select(self.xs2.root))
|
||||||
self.assertTrue(len(elements) == 17)
|
self.assertEqual(len(elements), 17)
|
||||||
|
|
||||||
def test_xpath_issues(self):
|
def test_xpath_issues(self):
|
||||||
namespaces = {'ps': "http://schemas.microsoft.com/powershell/2004/04"}
|
namespaces = {'ps': "http://schemas.microsoft.com/powershell/2004/04"}
|
||||||
|
|
|
@ -32,48 +32,54 @@ class XsdAssert(XsdComponent, ElementPathMixin):
|
||||||
"""
|
"""
|
||||||
_ADMITTED_TAGS = {XSD_ASSERT}
|
_ADMITTED_TAGS = {XSD_ASSERT}
|
||||||
token = None
|
token = None
|
||||||
|
parser = None
|
||||||
|
path = 'true()'
|
||||||
|
|
||||||
def __init__(self, elem, schema, parent, base_type):
|
def __init__(self, elem, schema, parent, base_type):
|
||||||
self.base_type = base_type
|
self.base_type = base_type
|
||||||
super(XsdAssert, self).__init__(elem, schema, parent)
|
super(XsdAssert, self).__init__(elem, schema, parent)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '%s(test=%r)' % (self.__class__.__name__, self.path)
|
||||||
|
|
||||||
def _parse(self):
|
def _parse(self):
|
||||||
super(XsdAssert, self)._parse()
|
super(XsdAssert, self)._parse()
|
||||||
if self.base_type.is_complex():
|
if self.base_type.is_simple():
|
||||||
|
self.parse_error("base_type=%r is not a complexType definition" % self.base_type)
|
||||||
|
else:
|
||||||
try:
|
try:
|
||||||
self.path = self.elem.attrib['test']
|
self.path = self.elem.attrib['test']
|
||||||
except KeyError as err:
|
except KeyError as err:
|
||||||
self.parse_error(str(err), elem=self.elem)
|
self.parse_error(str(err), elem=self.elem)
|
||||||
self.path = 'true()'
|
|
||||||
|
|
||||||
if not self.base_type.has_simple_content():
|
|
||||||
variables = {'value': datatypes.XSD_BUILTIN_TYPES['anyType'].value}
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
builtin_type_name = self.base_type.content_type.primitive_type.local_name
|
|
||||||
except AttributeError:
|
|
||||||
variables = {'value': datatypes.XSD_BUILTIN_TYPES['anySimpleType'].value}
|
|
||||||
else:
|
|
||||||
variables = {'value': datatypes.XSD_BUILTIN_TYPES[builtin_type_name].value}
|
|
||||||
|
|
||||||
else:
|
|
||||||
self.parse_error("base_type=%r is not a complexType definition" % self.base_type)
|
|
||||||
self.path = 'true()'
|
|
||||||
variables = None
|
|
||||||
|
|
||||||
if 'xpathDefaultNamespace' in self.elem.attrib:
|
if 'xpathDefaultNamespace' in self.elem.attrib:
|
||||||
self.xpath_default_namespace = self._parse_xpath_default_namespace(self.elem)
|
self.xpath_default_namespace = self._parse_xpath_default_namespace(self.elem)
|
||||||
else:
|
else:
|
||||||
self.xpath_default_namespace = self.schema.xpath_default_namespace
|
self.xpath_default_namespace = self.schema.xpath_default_namespace
|
||||||
self.parser = XPath2Parser(self.namespaces, variables, False,
|
|
||||||
self.xpath_default_namespace, schema=self.xpath_proxy)
|
self.xpath_proxy = XMLSchemaProxy(self.schema, self)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def built(self):
|
def built(self):
|
||||||
return self.token is not None and (self.base_type.parent is None or self.base_type.built)
|
return self.token is not None and (self.base_type.parent is None or self.base_type.built)
|
||||||
|
|
||||||
def parse_xpath_test(self):
|
def parse_xpath_test(self):
|
||||||
self.parser.schema = XMLSchemaProxy(self.schema, self)
|
if self.base_type.has_simple_content():
|
||||||
|
variables = {'value': datatypes.XSD_BUILTIN_TYPES['anyType'].value}
|
||||||
|
elif self.base_type.is_complex():
|
||||||
|
try:
|
||||||
|
builtin_type_name = self.base_type.content_type.primitive_type.local_name
|
||||||
|
except AttributeError:
|
||||||
|
variables = {'value': datatypes.XSD_BUILTIN_TYPES['anySimpleType'].value}
|
||||||
|
else:
|
||||||
|
variables = {'value': datatypes.XSD_BUILTIN_TYPES[builtin_type_name].value}
|
||||||
|
else:
|
||||||
|
variables = None
|
||||||
|
|
||||||
|
self.parser = XPath2Parser(
|
||||||
|
self.namespaces, variables, False, self.xpath_default_namespace, schema=self.xpath_proxy
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.token = self.parser.parse(self.path)
|
self.token = self.parser.parse(self.path)
|
||||||
except ElementPathError as err:
|
except ElementPathError as err:
|
||||||
|
@ -81,10 +87,16 @@ class XsdAssert(XsdComponent, ElementPathMixin):
|
||||||
self.token = self.parser.parse('true()')
|
self.token = self.parser.parse('true()')
|
||||||
|
|
||||||
def __call__(self, elem, value=None, source=None, **kwargs):
|
def __call__(self, elem, value=None, source=None, **kwargs):
|
||||||
self.parser.variables['value'] = value
|
if value is not None:
|
||||||
root = elem if source is None else source.root
|
self.parser.variables['value'] = self.base_type.text_decode(value)
|
||||||
|
|
||||||
|
if source is None:
|
||||||
|
context = XPathContext(root=elem)
|
||||||
|
else:
|
||||||
|
context = XPathContext(root=source.root, item=elem)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not self.token.evaluate(XPathContext(root=root, item=elem)):
|
if not self.token.evaluate(context.copy()):
|
||||||
msg = "expression is not true with test path %r."
|
msg = "expression is not true with test path %r."
|
||||||
yield XMLSchemaValidationError(self, obj=elem, reason=msg % self.path)
|
yield XMLSchemaValidationError(self, obj=elem, reason=msg % self.path)
|
||||||
except ElementPathError as err:
|
except ElementPathError as err:
|
||||||
|
|
|
@ -546,7 +546,7 @@ class XsdComplexType(XsdType, ValidationMixin):
|
||||||
for obj in self.base_type.iter_components(xsd_classes):
|
for obj in self.base_type.iter_components(xsd_classes):
|
||||||
yield obj
|
yield obj
|
||||||
|
|
||||||
for obj in self.assertions:
|
for obj in filter(lambda x: x.base_type is self, self.assertions):
|
||||||
if xsd_classes is None or isinstance(obj, xsd_classes):
|
if xsd_classes is None or isinstance(obj, xsd_classes):
|
||||||
yield obj
|
yield obj
|
||||||
|
|
||||||
|
@ -857,7 +857,7 @@ class Xsd11ComplexType(XsdComplexType):
|
||||||
|
|
||||||
def _parse_content_tail(self, elem, **kwargs):
|
def _parse_content_tail(self, elem, **kwargs):
|
||||||
self.attributes = self.schema.BUILDERS.attribute_group_class(elem, self.schema, self, **kwargs)
|
self.attributes = self.schema.BUILDERS.attribute_group_class(elem, self.schema, self, **kwargs)
|
||||||
self.assertions = []
|
|
||||||
for child in filter(lambda x: x.tag != XSD_ANNOTATION, elem):
|
self.assertions = [XsdAssert(e, self.schema, self, self) for e in elem if e.tag == XSD_ASSERT]
|
||||||
if child.tag == XSD_ASSERT:
|
if getattr(self.base_type, 'assertions', None):
|
||||||
self.assertions.append(XsdAssert(child, self.schema, self, self))
|
self.assertions.extend(assertion for assertion in self.base_type.assertions)
|
||||||
|
|
|
@ -643,6 +643,25 @@ class XsdPatternFacets(MutableSequence, XsdFacet):
|
||||||
return [e.get('value', '') for e in self._elements]
|
return [e.get('value', '') for e in self._elements]
|
||||||
|
|
||||||
|
|
||||||
|
class XsdAssertionXPathParser(XPath2Parser):
|
||||||
|
"""Parser for XSD 1.1 assertion facets."""
|
||||||
|
|
||||||
|
XsdAssertionXPathParser.unregister('last')
|
||||||
|
XsdAssertionXPathParser.unregister('position')
|
||||||
|
|
||||||
|
@XsdAssertionXPathParser.method(XsdAssertionXPathParser.function('last', nargs=0))
|
||||||
|
def evaluate(self, context=None):
|
||||||
|
self.missing_context("Context item size is undefined")
|
||||||
|
|
||||||
|
|
||||||
|
@XsdAssertionXPathParser.method(XsdAssertionXPathParser.function('position', nargs=0))
|
||||||
|
def evaluate(self, context=None):
|
||||||
|
self.missing_context("Context item position is undefined")
|
||||||
|
|
||||||
|
|
||||||
|
XsdAssertionXPathParser.build_tokenizer()
|
||||||
|
|
||||||
|
|
||||||
class XsdAssertionFacet(XsdFacet):
|
class XsdAssertionFacet(XsdFacet):
|
||||||
"""
|
"""
|
||||||
XSD 1.1 *assertion* facet for simpleType definitions.
|
XSD 1.1 *assertion* facet for simpleType definitions.
|
||||||
|
@ -678,8 +697,8 @@ class XsdAssertionFacet(XsdFacet):
|
||||||
self.xpath_default_namespace = self._parse_xpath_default_namespace(self.elem)
|
self.xpath_default_namespace = self._parse_xpath_default_namespace(self.elem)
|
||||||
else:
|
else:
|
||||||
self.xpath_default_namespace = self.schema.xpath_default_namespace
|
self.xpath_default_namespace = self.schema.xpath_default_namespace
|
||||||
self.parser = XPath2Parser(self.namespaces, strict=False, variables=variables,
|
self.parser = XsdAssertionXPathParser(self.namespaces, strict=False, variables=variables,
|
||||||
default_namespace=self.xpath_default_namespace)
|
default_namespace=self.xpath_default_namespace)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.token = self.parser.parse(self.path)
|
self.token = self.parser.parse(self.path)
|
||||||
|
|
|
@ -814,7 +814,7 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
|
||||||
|
|
||||||
def get_element(self, tag, path=None, namespaces=None):
|
def get_element(self, tag, path=None, namespaces=None):
|
||||||
if not path:
|
if not path:
|
||||||
return self.find(tag)
|
return self.find(tag, namespaces)
|
||||||
elif path[-1] == '*':
|
elif path[-1] == '*':
|
||||||
return self.find(path[:-1] + tag, namespaces)
|
return self.find(path[:-1] + tag, namespaces)
|
||||||
else:
|
else:
|
||||||
|
@ -1185,7 +1185,7 @@ class XMLSchemaBase(XsdValidator, ValidationMixin, ElementPathMixin):
|
||||||
schema_path = '/%s/*' % source.root.tag
|
schema_path = '/%s/*' % source.root.tag
|
||||||
|
|
||||||
for elem in source.iterfind(path, namespaces):
|
for elem in source.iterfind(path, namespaces):
|
||||||
xsd_element = self.get_element(elem.tag, schema_path, namespaces)
|
xsd_element = self.get_element(elem.tag, schema_path, self.namespaces)
|
||||||
if xsd_element is None:
|
if xsd_element is None:
|
||||||
yield self.validation_error('lax', "%r is not an element of the schema" % elem, elem)
|
yield self.validation_error('lax', "%r is not an element of the schema" % elem, elem)
|
||||||
|
|
||||||
|
|
|
@ -213,7 +213,7 @@ class ElementPathMixin(Sequence):
|
||||||
default_namespace=self.xpath_default_namespace)
|
default_namespace=self.xpath_default_namespace)
|
||||||
root_token = parser.parse(path)
|
root_token = parser.parse(path)
|
||||||
context = XMLSchemaContext(self)
|
context = XMLSchemaContext(self)
|
||||||
return root_token.select(context)
|
return root_token.select_results(context)
|
||||||
|
|
||||||
def find(self, path, namespaces=None):
|
def find(self, path, namespaces=None):
|
||||||
"""
|
"""
|
||||||
|
@ -226,14 +226,17 @@ class ElementPathMixin(Sequence):
|
||||||
path = path.strip()
|
path = path.strip()
|
||||||
if path.startswith('/') and not path.startswith('//'):
|
if path.startswith('/') and not path.startswith('//'):
|
||||||
path = ''.join(['/', XSD_SCHEMA, path])
|
path = ''.join(['/', XSD_SCHEMA, path])
|
||||||
|
|
||||||
if namespaces is None:
|
if namespaces is None:
|
||||||
namespaces = {k: v for k, v in self.namespaces.items() if k}
|
namespaces = {k: v for k, v in self.namespaces.items() if k}
|
||||||
|
namespaces[''] = self.xpath_default_namespace
|
||||||
|
elif '' not in namespaces:
|
||||||
|
namespaces[''] = self.xpath_default_namespace
|
||||||
|
|
||||||
parser = XPath2Parser(namespaces, strict=False, schema=self.xpath_proxy,
|
parser = XPath2Parser(namespaces, strict=False, schema=self.xpath_proxy)
|
||||||
default_namespace=self.xpath_default_namespace)
|
|
||||||
root_token = parser.parse(path)
|
root_token = parser.parse(path)
|
||||||
context = XMLSchemaContext(self)
|
context = XMLSchemaContext(self)
|
||||||
return next(root_token.select(context), None)
|
return next(root_token.select_results(context), None)
|
||||||
|
|
||||||
def findall(self, path, namespaces=None):
|
def findall(self, path, namespaces=None):
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in New Issue