801 lines
32 KiB
Python
801 lines
32 KiB
Python
# -*- coding: utf-8 -*-
|
|
#
|
|
# Copyright (c), 2016-2019, SISSA (International School for Advanced Studies).
|
|
# All rights reserved.
|
|
# This file is distributed under the terms of the MIT License.
|
|
# See the file 'LICENSE' in the root directory of the present
|
|
# distribution, or http://opensource.org/licenses/MIT.
|
|
#
|
|
# @author Davide Brunato <brunato@sissa.it>
|
|
#
|
|
"""
|
|
This module contains classes for XML Schema wildcards.
|
|
"""
|
|
from __future__ import unicode_literals
|
|
|
|
from ..exceptions import XMLSchemaValueError
|
|
from ..namespaces import XSI_NAMESPACE
|
|
from ..qnames import XSD_ANY, XSD_ANY_ATTRIBUTE, XSD_OPEN_CONTENT, \
|
|
XSD_DEFAULT_OPEN_CONTENT, get_namespace
|
|
from ..xpath import XMLSchemaProxy, ElementPathMixin
|
|
|
|
from .exceptions import XMLSchemaNotBuiltError
|
|
from .xsdbase import ValidationMixin, XsdComponent, ParticleMixin
|
|
|
|
|
|
class XsdWildcard(XsdComponent, ValidationMixin):
|
|
names = ()
|
|
namespace = ('##any',)
|
|
not_namespace = ()
|
|
not_qname = ()
|
|
process_contents = 'strict'
|
|
|
|
def __init__(self, elem, schema, parent):
|
|
if parent is None:
|
|
raise XMLSchemaValueError("'parent' attribute is None but %r cannot be global!" % self)
|
|
super(XsdWildcard, self).__init__(elem, schema, parent)
|
|
|
|
def __repr__(self):
|
|
if self.not_namespace:
|
|
return '%s(not_namespace=%r, process_contents=%r)' % (
|
|
self.__class__.__name__, self.not_namespace, self.process_contents
|
|
)
|
|
else:
|
|
return '%s(namespace=%r, process_contents=%r)' % (
|
|
self.__class__.__name__, self.namespace, self.process_contents
|
|
)
|
|
|
|
def _parse(self):
|
|
super(XsdWildcard, self)._parse()
|
|
|
|
# Parse namespace and processContents
|
|
namespace = self.elem.get('namespace', '##any').strip()
|
|
if namespace == '##any':
|
|
pass
|
|
elif not namespace:
|
|
self.namespace = [] # an empty value means no namespace allowed!
|
|
elif namespace == '##other':
|
|
self.namespace = [namespace]
|
|
elif namespace == '##local':
|
|
self.namespace = ['']
|
|
elif namespace == '##targetNamespace':
|
|
self.namespace = [self.target_namespace]
|
|
else:
|
|
self.namespace = []
|
|
for ns in namespace.split():
|
|
if ns == '##local':
|
|
self.namespace.append('')
|
|
elif ns == '##targetNamespace':
|
|
self.namespace.append(self.target_namespace)
|
|
elif ns.startswith('##'):
|
|
self.parse_error("wrong value %r in 'namespace' attribute" % ns)
|
|
else:
|
|
self.namespace.append(ns)
|
|
|
|
process_contents = self.elem.get('processContents', 'strict')
|
|
if process_contents == 'strict':
|
|
pass
|
|
elif process_contents not in ('lax', 'skip'):
|
|
self.parse_error("wrong value %r for 'processContents' attribute" % self.process_contents)
|
|
else:
|
|
self.process_contents = process_contents
|
|
|
|
def _parse_not_constraints(self):
|
|
if 'notNamespace' not in self.elem.attrib:
|
|
pass
|
|
elif 'namespace' in self.elem.attrib:
|
|
self.parse_error("'namespace' and 'notNamespace' attributes are mutually exclusive")
|
|
else:
|
|
self.namespace = []
|
|
self.not_namespace = []
|
|
for ns in self.elem.attrib['notNamespace'].strip().split():
|
|
if ns == '##local':
|
|
self.not_namespace.append('')
|
|
elif ns == '##targetNamespace':
|
|
self.not_namespace.append(self.target_namespace)
|
|
elif ns.startswith('##'):
|
|
self.parse_error("wrong value %r in 'notNamespace' attribute" % ns)
|
|
else:
|
|
self.not_namespace.append(ns)
|
|
|
|
# Parse notQName attribute
|
|
if 'notQName' not in self.elem.attrib:
|
|
return
|
|
|
|
not_qname = self.elem.attrib['notQName'].strip().split()
|
|
|
|
if isinstance(self, XsdAnyAttribute) and \
|
|
not all(not s.startswith('##') or s == '##defined' for s in not_qname) or \
|
|
not all(not s.startswith('##') or s in {'##defined', '##definedSibling'} for s in not_qname):
|
|
self.parse_error("wrong value for 'notQName' attribute")
|
|
return
|
|
|
|
try:
|
|
names = [x if x.startswith('##') else self.schema.resolve_qname(x, False)
|
|
for x in not_qname]
|
|
except KeyError as err:
|
|
self.parse_error("unmapped QName in 'notQName' attribute: %s" % str(err))
|
|
return
|
|
except ValueError as err:
|
|
self.parse_error("wrong QName format in 'notQName' attribute: %s" % str(err))
|
|
return
|
|
|
|
if self.not_namespace:
|
|
if any(not x.startswith('##') for x in names) and \
|
|
all(get_namespace(x) in self.not_namespace for x in names if not x.startswith('##')):
|
|
self.parse_error("the namespace of each QName in notQName is allowed by notNamespace")
|
|
elif any(not self.is_namespace_allowed(get_namespace(x)) for x in names if not x.startswith('##')):
|
|
self.parse_error("names in notQName must be in namespaces that are allowed")
|
|
|
|
self.not_qname = names
|
|
|
|
def _load_namespace(self, namespace):
|
|
if namespace in self.schema.maps.namespaces:
|
|
return
|
|
|
|
for url in self.schema.get_locations(namespace):
|
|
try:
|
|
schema = self.schema.import_schema(namespace, url, base_url=self.schema.base_url)
|
|
if schema is not None:
|
|
try:
|
|
schema.maps.build()
|
|
except XMLSchemaNotBuiltError:
|
|
# Namespace build fails: remove unbuilt schemas and the url hint
|
|
schema.maps.clear(remove_schemas=True, only_unbuilt=True)
|
|
self.schema.locations[namespace].remove(url)
|
|
else:
|
|
break
|
|
except (OSError, IOError):
|
|
pass
|
|
|
|
@property
|
|
def built(self):
|
|
return True
|
|
|
|
def is_matching(self, name, default_namespace=None, **kwargs):
|
|
if name is None:
|
|
return False
|
|
elif not name or name[0] == '{':
|
|
return self.is_namespace_allowed(get_namespace(name))
|
|
elif default_namespace is None:
|
|
return self.is_namespace_allowed('')
|
|
else:
|
|
return self.is_namespace_allowed(default_namespace)
|
|
|
|
def is_namespace_allowed(self, namespace):
|
|
if self.not_namespace:
|
|
return namespace not in self.not_namespace
|
|
elif '##any' in self.namespace or namespace == XSI_NAMESPACE:
|
|
return True
|
|
elif '##other' in self.namespace:
|
|
return namespace and namespace != self.target_namespace
|
|
else:
|
|
return namespace in self.namespace
|
|
|
|
def deny_namespaces(self, namespaces):
|
|
if self.not_namespace:
|
|
return all(x in self.not_namespace for x in namespaces)
|
|
elif '##any' in self.namespace:
|
|
return False
|
|
elif '##other' in self.namespace:
|
|
return all(x == self.target_namespace for x in namespaces)
|
|
else:
|
|
return all(x not in self.namespace for x in namespaces)
|
|
|
|
def deny_qnames(self, names):
|
|
if self.not_namespace:
|
|
return all(x in self.not_qname or get_namespace(x) in self.not_namespace for x in names)
|
|
elif '##any' in self.namespace:
|
|
return all(x in self.not_qname for x in names)
|
|
elif '##other' in self.namespace:
|
|
return all(x in self.not_qname or get_namespace(x) == self.target_namespace for x in names)
|
|
else:
|
|
return all(x in self.not_qname or get_namespace(x) not in self.namespace for x in names)
|
|
|
|
def is_restriction(self, other, check_occurs=True):
|
|
if check_occurs and isinstance(self, ParticleMixin) and not self.has_occurs_restriction(other):
|
|
return False
|
|
elif not isinstance(other, type(self)):
|
|
return False
|
|
elif other.process_contents == 'strict' and self.process_contents != 'strict':
|
|
return False
|
|
elif other.process_contents == 'lax' and self.process_contents == 'skip':
|
|
return False
|
|
|
|
if not self.not_qname and not other.not_qname:
|
|
pass
|
|
elif '##defined' in other.not_qname and '##defined' not in self.not_qname:
|
|
return False
|
|
elif '##definedSibling' in other.not_qname and '##definedSibling' not in self.not_qname:
|
|
return False
|
|
elif other.not_qname:
|
|
if not self.deny_qnames(x for x in other.not_qname if not x.startswith('##')):
|
|
return False
|
|
elif any(not other.is_namespace_allowed(get_namespace(x))
|
|
for x in self.not_qname if not x.startswith('##')):
|
|
return False
|
|
|
|
if self.not_namespace:
|
|
if other.not_namespace:
|
|
return all(ns in self.not_namespace for ns in other.not_namespace)
|
|
elif '##any' in other.namespace:
|
|
return True
|
|
elif '##other' in other.namespace:
|
|
return '' in self.not_namespace and other.target_namespace in self.not_namespace
|
|
else:
|
|
return False
|
|
elif other.not_namespace:
|
|
if '##any' in self.namespace:
|
|
return False
|
|
elif '##other' in self.namespace:
|
|
return set(other.not_namespace).issubset({'', other.target_namespace})
|
|
else:
|
|
return all(ns not in other.not_namespace for ns in self.namespace)
|
|
|
|
if self.namespace == other.namespace:
|
|
return True
|
|
elif '##any' in other.namespace:
|
|
return True
|
|
elif '##any' in self.namespace or '##other' in self.namespace:
|
|
return False
|
|
elif '##other' in other.namespace:
|
|
return other.target_namespace not in self.namespace and '' not in self.namespace
|
|
else:
|
|
return all(ns in other.namespace for ns in self.namespace)
|
|
|
|
def union(self, other):
|
|
"""
|
|
Update an XSD wildcard with the union of itself and another XSD wildcard.
|
|
"""
|
|
if not self.not_qname:
|
|
self.not_qname = other.not_qname[:]
|
|
else:
|
|
self.not_qname = [
|
|
x for x in self.not_qname
|
|
if x in other.not_qname or not other.is_namespace_allowed(get_namespace(x))
|
|
]
|
|
|
|
if self.not_namespace:
|
|
if other.not_namespace:
|
|
self.not_namespace = [ns for ns in self.not_namespace if ns in other.not_namespace]
|
|
elif '##any' in other.namespace:
|
|
self.not_namespace = []
|
|
self.namespace = ['##any']
|
|
return
|
|
elif '##other' in other.namespace:
|
|
not_namespace = ('', other.target_namespace)
|
|
self.not_namespace = [ns for ns in self.not_namespace if ns in not_namespace]
|
|
else:
|
|
self.not_namespace = [ns for ns in self.not_namespace if ns not in other.namespace]
|
|
|
|
if not self.not_namespace:
|
|
self.namespace = ['##any']
|
|
return
|
|
|
|
elif other.not_namespace:
|
|
if '##any' in self.namespace:
|
|
return
|
|
elif '##other' in self.namespace:
|
|
not_namespace = ('', self.target_namespace)
|
|
self.not_namespace = [ns for ns in other.not_namespace if ns in not_namespace]
|
|
else:
|
|
self.not_namespace = [ns for ns in other.not_namespace if ns not in self.namespace]
|
|
|
|
self.namespace = ['##any'] if not self.not_namespace else []
|
|
return
|
|
|
|
if '##any' in self.namespace or self.namespace == other.namespace:
|
|
return
|
|
elif '##any' in other.namespace:
|
|
self.namespace = ['##any']
|
|
return
|
|
elif '##other' in other.namespace:
|
|
w1, w2 = other, self
|
|
elif '##other' in self.namespace:
|
|
w1, w2 = self, other
|
|
else:
|
|
self.namespace.extend(ns for ns in other.namespace if ns not in self.namespace)
|
|
return
|
|
|
|
if w1.target_namespace in w2.namespace and '' in w2.namespace:
|
|
self.namespace = ['##any']
|
|
elif '' not in w2.namespace and w1.target_namespace == w2.target_namespace:
|
|
self.namespace = ['##other']
|
|
elif self.xsd_version == '1.0':
|
|
msg = "not expressible wildcard namespace union: {!r} V {!r}:"
|
|
raise XMLSchemaValueError(msg.format(other.namespace, self.namespace))
|
|
else:
|
|
self.namespace = []
|
|
self.not_namespace = ['', w1.target_namespace] if w1.target_namespace else ['']
|
|
|
|
def intersection(self, other):
|
|
"""
|
|
Update an XSD wildcard with the intersection of itself and another XSD wildcard.
|
|
"""
|
|
if self.not_qname:
|
|
self.not_qname.extend(x for x in other.not_qname if x not in self.not_qname)
|
|
else:
|
|
self.not_qname = [x for x in other.not_qname]
|
|
|
|
if self.not_namespace:
|
|
if other.not_namespace:
|
|
self.not_namespace.extend(ns for ns in other.not_namespace if ns not in self.not_namespace)
|
|
elif '##any' in other.namespace:
|
|
pass
|
|
elif '##other' not in other.namespace:
|
|
self.namespace = [ns for ns in other.namespace if ns not in self.not_namespace]
|
|
self.not_namespace = []
|
|
else:
|
|
if other.target_namespace not in self.not_namespace:
|
|
self.not_namespace.append(other.target_namespace)
|
|
if '' not in self.not_namespace:
|
|
self.not_namespace.append('')
|
|
return
|
|
|
|
elif other.not_namespace:
|
|
if '##any' in self.namespace:
|
|
self.not_namespace = [ns for ns in other.not_namespace]
|
|
self.namespace = []
|
|
elif '##other' not in self.namespace:
|
|
self.namespace = [ns for ns in self.namespace if ns not in other.not_namespace]
|
|
else:
|
|
self.not_namespace = [ns for ns in other.not_namespace]
|
|
if self.target_namespace not in self.not_namespace:
|
|
self.not_namespace.append(self.target_namespace)
|
|
if '' not in self.not_namespace:
|
|
self.not_namespace.append('')
|
|
self.namespace = []
|
|
return
|
|
|
|
if self.namespace == other.namespace:
|
|
return
|
|
elif '##any' in other.namespace:
|
|
return
|
|
elif '##any' in self.namespace:
|
|
self.namespace = other.namespace[:]
|
|
elif '##other' in self.namespace:
|
|
self.namespace = [ns for ns in other.namespace if ns not in ('', self.target_namespace)]
|
|
elif '##other' not in other.namespace:
|
|
self.namespace = [ns for ns in self.namespace if ns in other.namespace]
|
|
else:
|
|
if other.target_namespace in self.namespace:
|
|
self.namespace.remove(other.target_namespace)
|
|
if '' in self.namespace:
|
|
self.namespace.remove('')
|
|
|
|
def iter_decode(self, source, validation='lax', **kwargs):
|
|
raise NotImplementedError
|
|
|
|
def iter_encode(self, obj, validation='lax', **kwargs):
|
|
raise NotImplementedError
|
|
|
|
|
|
class XsdAnyElement(XsdWildcard, ParticleMixin, ElementPathMixin):
|
|
"""
|
|
Class for XSD 1.0 *any* wildcards.
|
|
|
|
.. <any
|
|
id = ID
|
|
maxOccurs = (nonNegativeInteger | unbounded) : 1
|
|
minOccurs = nonNegativeInteger : 1
|
|
namespace = ((##any | ##other) | List of (anyURI | (##targetNamespace | ##local)) ) : ##any
|
|
processContents = (lax | skip | strict) : strict
|
|
{any attributes with non-schema namespace . . .}>
|
|
Content: (annotation?)
|
|
</any>
|
|
"""
|
|
_ADMITTED_TAGS = {XSD_ANY}
|
|
precedences = ()
|
|
|
|
def __repr__(self):
|
|
if self.namespace:
|
|
return '%s(namespace=%r, process_contents=%r, occurs=%r)' % (
|
|
self.__class__.__name__, self.namespace, self.process_contents, self.occurs
|
|
)
|
|
else:
|
|
return '%s(not_namespace=%r, process_contents=%r, occurs=%r)' % (
|
|
self.__class__.__name__, self.not_namespace, self.process_contents, self.occurs
|
|
)
|
|
|
|
@property
|
|
def xpath_proxy(self):
|
|
return XMLSchemaProxy(self.schema, self)
|
|
|
|
def _parse(self):
|
|
super(XsdAnyElement, self)._parse()
|
|
self._parse_particle(self.elem)
|
|
|
|
def match(self, name, default_namespace=None, resolve=False, **kwargs):
|
|
"""
|
|
Returns the element wildcard if name is matching the name provided
|
|
as argument, `None` otherwise.
|
|
|
|
:param name: a local or fully-qualified name.
|
|
:param default_namespace: used when it's not `None` and not empty for \
|
|
completing local name arguments.
|
|
:param resolve: when `True` it doesn't return the wildcard but try to \
|
|
resolve and return the element matching the name.
|
|
:param kwargs: additional options used by XSD 1.1 xs:any wildcards.
|
|
"""
|
|
if not self.is_matching(name, default_namespace, **kwargs):
|
|
return
|
|
elif not resolve:
|
|
return self
|
|
|
|
try:
|
|
if name[0] != '{' and default_namespace:
|
|
return self.maps.lookup_element('{%s}%s' % (default_namespace, name))
|
|
else:
|
|
return self.maps.lookup_element(name)
|
|
except LookupError:
|
|
pass
|
|
|
|
def __iter__(self):
|
|
return iter(())
|
|
|
|
def iter(self, tag=None):
|
|
return iter(())
|
|
|
|
def iterchildren(self, tag=None):
|
|
return iter(())
|
|
|
|
@staticmethod
|
|
def iter_substitutes():
|
|
return iter(())
|
|
|
|
def iter_decode(self, elem, validation='lax', **kwargs):
|
|
if self.is_matching(elem.tag):
|
|
if self.process_contents == 'skip':
|
|
return
|
|
|
|
self._load_namespace(get_namespace(elem.tag))
|
|
try:
|
|
xsd_element = self.maps.lookup_element(elem.tag)
|
|
except LookupError:
|
|
if kwargs.get('drop_results'):
|
|
# Validation-only mode: use anyType for decode a complex element.
|
|
yield self.any_type.decode(elem) if len(elem) > 0 else elem.text
|
|
elif self.process_contents == 'strict' and validation != 'skip':
|
|
reason = "element %r not found." % elem.tag
|
|
yield self.validation_error(validation, reason, elem, **kwargs)
|
|
else:
|
|
for result in xsd_element.iter_decode(elem, validation, **kwargs):
|
|
yield result
|
|
elif validation != 'skip':
|
|
reason = "element %r not allowed here." % elem.tag
|
|
yield self.validation_error(validation, reason, elem, **kwargs)
|
|
|
|
def iter_encode(self, obj, validation='lax', **kwargs):
|
|
if self.process_contents == 'skip':
|
|
return
|
|
|
|
name, value = obj
|
|
namespace = get_namespace(name)
|
|
|
|
if self.is_namespace_allowed(namespace):
|
|
self._load_namespace(namespace)
|
|
try:
|
|
xsd_element = self.maps.lookup_element(name)
|
|
except LookupError:
|
|
if self.process_contents == 'strict' and validation != 'skip':
|
|
reason = "element %r not found." % name
|
|
yield self.validation_error(validation, reason, **kwargs)
|
|
else:
|
|
for result in xsd_element.iter_encode(value, validation, **kwargs):
|
|
yield result
|
|
elif validation != 'skip':
|
|
reason = "element %r not allowed here." % name
|
|
yield self.validation_error(validation, reason, value, **kwargs)
|
|
|
|
def is_overlap(self, other):
|
|
if not isinstance(other, XsdAnyElement):
|
|
return other.is_overlap(self)
|
|
elif self.not_namespace:
|
|
if other.not_namespace:
|
|
return True
|
|
elif '##any' in other.namespace:
|
|
return True
|
|
elif '##other' in other.namespace:
|
|
return True
|
|
else:
|
|
return any(ns not in self.not_namespace for ns in other.namespace)
|
|
elif other.not_namespace:
|
|
if '##any' in self.namespace:
|
|
return True
|
|
elif '##other' in self.namespace:
|
|
return True
|
|
else:
|
|
return any(ns not in other.not_namespace for ns in self.namespace)
|
|
elif self.namespace == other.namespace:
|
|
return True
|
|
elif '##any' in self.namespace or '##any' in other.namespace:
|
|
return True
|
|
elif '##other' in self.namespace:
|
|
return any(ns and ns != self.target_namespace for ns in other.namespace)
|
|
elif '##other' in other.namespace:
|
|
return any(ns and ns != other.target_namespace for ns in self.namespace)
|
|
else:
|
|
return any(ns in self.namespace for ns in other.namespace)
|
|
|
|
def is_consistent(self, other):
|
|
return True
|
|
|
|
|
|
class XsdAnyAttribute(XsdWildcard):
|
|
"""
|
|
Class for XSD 1.0 *anyAttribute* wildcards.
|
|
|
|
.. <anyAttribute
|
|
id = ID
|
|
namespace = ((##any | ##other) | List of (anyURI | (##targetNamespace | ##local)) )
|
|
processContents = (lax | skip | strict) : strict
|
|
{any attributes with non-schema namespace . . .}>
|
|
Content: (annotation?)
|
|
</anyAttribute>
|
|
"""
|
|
_ADMITTED_TAGS = {XSD_ANY_ATTRIBUTE}
|
|
|
|
def match(self, name, default_namespace=None, resolve=False, **kwargs):
|
|
"""
|
|
Returns the attribute wildcard if name is matching the name provided
|
|
as argument, `None` otherwise.
|
|
|
|
:param name: a local or fully-qualified name.
|
|
:param default_namespace: used when it's not `None` and not empty for \
|
|
completing local name arguments.
|
|
:param resolve: when `True` it doesn't return the wildcard but try to \
|
|
resolve and return the attribute matching the name.
|
|
:param kwargs: additional options that can be used by certain components.
|
|
"""
|
|
if not self.is_matching(name, default_namespace, **kwargs):
|
|
return
|
|
elif not resolve:
|
|
return self
|
|
|
|
try:
|
|
if name[0] != '{' and default_namespace:
|
|
return self.maps.lookup_attribute('{%s}%s' % (default_namespace, name))
|
|
else:
|
|
return self.maps.lookup_attribute(name)
|
|
except LookupError:
|
|
pass
|
|
|
|
def iter_decode(self, attribute, validation='lax', **kwargs):
|
|
name, value = attribute
|
|
if self.is_matching(name):
|
|
if self.process_contents == 'skip':
|
|
return
|
|
|
|
self._load_namespace(get_namespace(name))
|
|
try:
|
|
xsd_attribute = self.maps.lookup_attribute(name)
|
|
except LookupError:
|
|
if kwargs.get('drop_results'):
|
|
# Validation-only mode: returns the value if a decoder is not found.
|
|
yield value
|
|
elif self.process_contents == 'strict' and validation != 'skip':
|
|
reason = "attribute %r not found." % name
|
|
yield self.validation_error(validation, reason, attribute, **kwargs)
|
|
else:
|
|
for result in xsd_attribute.iter_decode(value, validation, **kwargs):
|
|
yield result
|
|
elif validation != 'skip':
|
|
reason = "attribute %r not allowed." % name
|
|
yield self.validation_error(validation, reason, attribute, **kwargs)
|
|
|
|
def iter_encode(self, attribute, validation='lax', **kwargs):
|
|
if self.process_contents == 'skip':
|
|
return
|
|
|
|
name, value = attribute
|
|
namespace = get_namespace(name)
|
|
if self.is_namespace_allowed(namespace):
|
|
self._load_namespace(namespace)
|
|
try:
|
|
xsd_attribute = self.maps.lookup_attribute(name)
|
|
except LookupError:
|
|
if self.process_contents == 'strict' and validation != 'skip':
|
|
reason = "attribute %r not found." % name
|
|
yield self.validation_error(validation, reason, attribute, **kwargs)
|
|
else:
|
|
for result in xsd_attribute.iter_encode(value, validation, **kwargs):
|
|
yield result
|
|
elif validation != 'skip':
|
|
reason = "attribute %r not allowed." % name
|
|
yield self.validation_error(validation, reason, attribute, **kwargs)
|
|
|
|
|
|
class Xsd11AnyElement(XsdAnyElement):
|
|
"""
|
|
Class for XSD 1.1 *any* declarations.
|
|
|
|
.. <any
|
|
id = ID
|
|
maxOccurs = (nonNegativeInteger | unbounded) : 1
|
|
minOccurs = nonNegativeInteger : 1
|
|
namespace = ((##any | ##other) | List of (anyURI | (##targetNamespace | ##local)) )
|
|
notNamespace = List of (anyURI | (##targetNamespace | ##local))
|
|
notQName = List of (QName | (##defined | ##definedSibling))
|
|
processContents = (lax | skip | strict) : strict
|
|
{any attributes with non-schema namespace . . .}>
|
|
Content: (annotation?)
|
|
</any>
|
|
"""
|
|
def _parse(self):
|
|
super(Xsd11AnyElement, self)._parse()
|
|
self._parse_not_constraints()
|
|
|
|
def is_matching(self, name, default_namespace=None, group=None, occurs=None):
|
|
"""
|
|
Returns `True` if the component name is matching the name provided as argument,
|
|
`False` otherwise. For XSD elements the matching is extended to substitutes.
|
|
|
|
:param name: a local or fully-qualified name.
|
|
:param default_namespace: used if it's not None and not empty for completing \
|
|
the name argument in case it's a local name.
|
|
:param group: used only by XSD 1.1 any element wildcards to verify siblings in \
|
|
case of ##definedSibling value in notQName attribute.
|
|
:param occurs: a Counter instance for verify model occurrences counting.
|
|
"""
|
|
if name is None:
|
|
return False
|
|
elif not name or name[0] == '{':
|
|
namespace = get_namespace(name)
|
|
elif default_namespace is None:
|
|
namespace = ''
|
|
else:
|
|
name = '{%s}%s' % (default_namespace, name)
|
|
namespace = default_namespace
|
|
|
|
if group in self.precedences:
|
|
if occurs is None:
|
|
if any(e.is_matching(name) for e in self.precedences[group]):
|
|
return False
|
|
elif any(e.is_matching(name) and not e.is_over(occurs[e]) for e in self.precedences[group]):
|
|
return False
|
|
|
|
if '##defined' in self.not_qname and name in self.maps.elements:
|
|
return False
|
|
if group and '##definedSibling' in self.not_qname:
|
|
if any(e.is_matching(name) for e in group.iter_elements()
|
|
if not isinstance(e, XsdAnyElement)):
|
|
return False
|
|
return name not in self.not_qname and self.is_namespace_allowed(namespace)
|
|
|
|
def is_consistent(self, other):
|
|
if isinstance(other, XsdAnyElement) or self.process_contents == 'skip':
|
|
return True
|
|
xsd_element = self.match(other.name, other.default_namespace, resolve=True)
|
|
return xsd_element is None or other.is_consistent(xsd_element, strict=False)
|
|
|
|
def add_precedence(self, other, group):
|
|
if not self.precedences:
|
|
self.precedences = {}
|
|
try:
|
|
self.precedences[group].append(other)
|
|
except KeyError:
|
|
self.precedences[group] = [other]
|
|
|
|
|
|
class Xsd11AnyAttribute(XsdAnyAttribute):
|
|
"""
|
|
Class for XSD 1.1 *anyAttribute* declarations.
|
|
|
|
.. <anyAttribute
|
|
id = ID
|
|
namespace = ((##any | ##other) | List of (anyURI | (##targetNamespace | ##local)) )
|
|
notNamespace = List of (anyURI | (##targetNamespace | ##local))
|
|
notQName = List of (QName | ##defined)
|
|
processContents = (lax | skip | strict) : strict
|
|
{any attributes with non-schema namespace . . .}>
|
|
Content: (annotation?)
|
|
</anyAttribute>
|
|
"""
|
|
inheritable = False # Added for reduce checkings on XSD 1.1 attributes
|
|
|
|
def _parse(self):
|
|
super(Xsd11AnyAttribute, self)._parse()
|
|
self._parse_not_constraints()
|
|
|
|
def is_matching(self, name, default_namespace=None, **kwargs):
|
|
if name is None:
|
|
return False
|
|
elif not name or name[0] == '{':
|
|
namespace = get_namespace(name)
|
|
elif default_namespace is None:
|
|
namespace = ''
|
|
else:
|
|
name = '{%s}%s' % (default_namespace, name)
|
|
namespace = default_namespace
|
|
|
|
if '##defined' in self.not_qname and name in self.maps.attributes:
|
|
return False
|
|
return name not in self.not_qname and self.is_namespace_allowed(namespace)
|
|
|
|
|
|
class XsdOpenContent(XsdComponent):
|
|
"""
|
|
Class for XSD 1.1 *openContent* model definitions.
|
|
|
|
.. <openContent
|
|
id = ID
|
|
mode = (none | interleave | suffix) : interleave
|
|
{any attributes with non-schema namespace . . .}>
|
|
Content: (annotation?), (any?)
|
|
</openContent>
|
|
"""
|
|
_ADMITTED_TAGS = {XSD_OPEN_CONTENT}
|
|
mode = 'interleave'
|
|
any_element = None
|
|
|
|
def __init__(self, elem, schema, parent):
|
|
super(XsdOpenContent, self).__init__(elem, schema, parent)
|
|
|
|
def __repr__(self):
|
|
return '%s(mode=%r)' % (self.__class__.__name__, self.mode)
|
|
|
|
def _parse(self):
|
|
super(XsdOpenContent, self)._parse()
|
|
try:
|
|
self.mode = self.elem.attrib['mode']
|
|
except KeyError:
|
|
pass
|
|
else:
|
|
if self.mode not in {'none', 'interleave', 'suffix'}:
|
|
self.parse_error("wrong value %r for 'mode' attribute." % self.mode)
|
|
|
|
child = self._parse_child_component(self.elem)
|
|
if self.mode == 'none':
|
|
if child is not None and child.tag == XSD_ANY:
|
|
self.parse_error("an openContent with mode='none' must not has an <xs:any> child declaration")
|
|
elif child is None or child.tag != XSD_ANY:
|
|
self.parse_error("an <xs:any> child declaration is required")
|
|
else:
|
|
any_element = Xsd11AnyElement(child, self.schema, self)
|
|
any_element.min_occurs = 0
|
|
any_element.max_occurs = None
|
|
self.any_element = any_element
|
|
|
|
@property
|
|
def built(self):
|
|
return True
|
|
|
|
def is_restriction(self, other):
|
|
if self.mode == 'none' or other is None or other.mode == 'none':
|
|
return True
|
|
elif self.mode == 'interleave' and other.mode == 'suffix':
|
|
return False
|
|
else:
|
|
return self.any_element.is_restriction(other.any_element)
|
|
|
|
|
|
class XsdDefaultOpenContent(XsdOpenContent):
|
|
"""
|
|
Class for XSD 1.1 *defaultOpenContent* model definitions.
|
|
|
|
.. <defaultOpenContent
|
|
appliesToEmpty = boolean : false
|
|
id = ID
|
|
mode = (interleave | suffix) : interleave
|
|
{any attributes with non-schema namespace . . .}>
|
|
Content: (annotation?, any)
|
|
</defaultOpenContent>
|
|
"""
|
|
_ADMITTED_TAGS = {XSD_DEFAULT_OPEN_CONTENT}
|
|
applies_to_empty = False
|
|
|
|
def __init__(self, elem, schema):
|
|
super(XsdOpenContent, self).__init__(elem, schema)
|
|
|
|
def _parse(self):
|
|
super(XsdDefaultOpenContent, self)._parse()
|
|
if self.parent is not None:
|
|
self.parse_error("defaultOpenContent must be a child of the schema")
|
|
if self.mode == 'none':
|
|
self.parse_error("the attribute 'mode' of a defaultOpenContent cannot be 'none'")
|
|
if self._parse_child_component(self.elem) is None:
|
|
self.parse_error("a defaultOpenContent declaration cannot be empty")
|
|
|
|
if self._parse_boolean_attribute('appliesToEmpty'):
|
|
self.applies_to_empty = True
|