diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..f078c55 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,4 @@ +[run] +branch = True +source = xmlschema/ +omit = xmlschema/tests/* diff --git a/.gitignore b/.gitignore index 6201f9e..3c1ce44 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ *.json .idea/ .tox/ +.coverage .ipynb_checkpoints/ doc/_*/ dist/ diff --git a/requirements-dev.txt b/requirements-dev.txt index 2bc9058..926cb6b 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,6 +1,7 @@ # Requirements for setup a development environment for the xmlschema package. setuptools tox +coverage elementpath~=1.1.7 lxml memory_profiler diff --git a/tox.ini b/tox.ini index c271fe3..75e7a79 100644 --- a/tox.ini +++ b/tox.ini @@ -4,14 +4,20 @@ # and then run "tox" from this directory. [tox] -envlist = py27, py34, py35, py36, py37 +envlist = py27, py35, py36, py37, py38, docs, flake8, coverage +skip_missing_interpreters = true toxworkdir = {homedir}/.tox/xmlschema [testenv] deps = lxml elementpath~=1.1.7 + docs: Sphinx + docs: sphinx_rtd_theme + flake8: flake8 + coverage: coverage commands = python xmlschema/tests/test_all.py {posargs} +whitelist_externals = make [testenv:py27] deps = @@ -19,3 +25,35 @@ deps = elementpath~=1.1.7 pathlib2 commands = python xmlschema/tests/test_all.py {posargs} + +[testenv:py38] +deps = elementpath~=1.1.7 +commands = python xmlschema/tests/test_all.py {posargs} + +[testenv:docs] +commands = + make -C doc html + make -C doc latexpdf + make -C doc doctest + +[flake8] +max-line-length = 119 + +[testenv:flake8] +commands = + flake8 --ignore=F401,F403,F405,F811,F821 xmlschema + +[testenv:coverage] +commands = + coverage run -p -m unittest + coverage combine + coverage report -m + +[testenv:build] +deps = + setuptools + wheel +commands = + python setup.py clean --all + python setup.py sdist --dist-dir {toxinidir}/dist + python setup.py bdist_wheel --universal --dist-dir {toxinidir}/dist diff --git a/xmlschema/__init__.py b/xmlschema/__init__.py index 999b25c..e9e8363 100644 --- a/xmlschema/__init__.py +++ b/xmlschema/__init__.py @@ -37,7 +37,7 @@ __status__ = "Production/Stable" # API deprecation warnings def XMLSchema_v1_0(*args, **kwargs): import warnings - warnings.warn("XMLSchema_v1_0 class name has been replaced by XMLSchema10 " + warnings.warn("XMLSchema_v1_0 class name has been replaced by XMLSchema10 " "and will be removed in 1.1 version", DeprecationWarning, stacklevel=2) return XMLSchema10(*args, **kwargs) diff --git a/xmlschema/codepoints.py b/xmlschema/codepoints.py index 3f803d7..84dc04e 100644 --- a/xmlschema/codepoints.py +++ b/xmlschema/codepoints.py @@ -165,18 +165,18 @@ def iterparse_character_group(s, expand_ranges=False): k = next(string_iter) end_char = s[k] if end_char == '\\' and (k < length - 1): - if s[k+1] in r'-|.^?*+{}()[]': + if s[k + 1] in r'-|.^?*+{}()[]': k = next(string_iter) end_char = s[k] - elif s[k+1] in r'sSdDiIcCwWpP': - msg = "bad character range '%s-\\%s' at position %d: %r" % (char, s[k+1], k-2, s) + elif s[k + 1] in r'sSdDiIcCwWpP': + msg = "bad character range '%s-\\%s' at position %d: %r" % (char, s[k + 1], k - 2, s) raise XMLSchemaRegexError(msg) except StopIteration: - msg = "bad character range '%s-%s' at position %d: %r" % (char, s[-1], k-2, s) + msg = "bad character range '%s-%s' at position %d: %r" % (char, s[-1], k - 2, s) raise XMLSchemaRegexError(msg) if ord(char) > ord(end_char): - msg = "bad character range '%s-%s' at position %d: %r" % (char, end_char, k-2, s) + msg = "bad character range '%s-%s' at position %d: %r" % (char, end_char, k - 2, s) raise XMLSchemaRegexError(msg) elif expand_ranges: for cp in range(ord(char) + 1, ord(end_char) + 1): @@ -194,7 +194,7 @@ def iterparse_character_group(s, expand_ranges=False): raise XMLSchemaRegexError("bad character %r at position %d" % (s[k], k)) escaped = on_range = False char = s[k] - if k >= length-1 or s[k+1] != '-': + if k >= length - 1 or s[k + 1] != '-': yield ord(char) elif s[k] == '\\': if escaped: @@ -209,7 +209,7 @@ def iterparse_character_group(s, expand_ranges=False): yield ord('\\') on_range = False char = s[k] - if k >= length-1 or s[k+1] != '-': + if k >= length - 1 or s[k + 1] != '-': yield ord(char) if escaped: yield ord('\\') diff --git a/xmlschema/converters.py b/xmlschema/converters.py index 6e1ce14..1a4f404 100644 --- a/xmlschema/converters.py +++ b/xmlschema/converters.py @@ -145,7 +145,7 @@ class XMLSchemaConverter(NamespaceMapper): """ if not content: return - + map_qname = self.map_qname for name, value, xsd_child in content: try: @@ -274,7 +274,7 @@ class XMLSchemaConverter(NamespaceMapper): elif name == ns_prefix: self[''] = value elif name.startswith('%s:' % ns_prefix): - self[name[len(ns_prefix)+1:]] = value + self[name[len(ns_prefix) + 1:]] = value elif attr_prefix and name.startswith(attr_prefix): name = name[len(attr_prefix):] attributes[unmap_attribute_qname(name)] = value @@ -314,6 +314,7 @@ class ParkerConverter(XMLSchemaConverter): XML Schema based converter class for Parker convention. ref: http://wiki.open311.org/JSON_and_XML_Conversion/#the-parker-convention + ref: https://developer.mozilla.org/en-US/docs/Archive/JXON#The_Parker_Convention :param namespaces: Map from namespace prefixes to URI. :param dict_class: Dictionary class to use for decoded data. Default is `dict` for \ @@ -780,8 +781,8 @@ class JsonMLConverter(XMLSchemaConverter): if data_len <= content_index: return ElementData(xsd_element.name, None, [], attributes) - elif data_len == content_index + 1 and (xsd_element.type.is_simple() - or xsd_element.type.has_simple_content()): + elif data_len == content_index + 1 and \ + (xsd_element.type.is_simple() or xsd_element.type.has_simple_content()): return ElementData(xsd_element.name, obj[content_index], [], attributes) else: cdata_num = iter(range(1, data_len)) diff --git a/xmlschema/exceptions.py b/xmlschema/exceptions.py index f3d78ae..964bca9 100644 --- a/xmlschema/exceptions.py +++ b/xmlschema/exceptions.py @@ -56,4 +56,3 @@ class XMLSchemaRegexError(XMLSchemaException, ValueError): class XMLSchemaWarning(Warning): """Base warning class for the XMLSchema package.""" - diff --git a/xmlschema/regex.py b/xmlschema/regex.py index e28b806..b7d59ed 100644 --- a/xmlschema/regex.py +++ b/xmlschema/regex.py @@ -51,8 +51,8 @@ I_SHORTCUT_SET = UnicodeSubset(I_SHORTCUT_REPLACE) C_SHORTCUT_SET = UnicodeSubset(C_SHORTCUT_REPLACE) W_SHORTCUT_SET = UnicodeSubset() W_SHORTCUT_SET._code_points = sorted( - UNICODE_CATEGORIES['P'].code_points + UNICODE_CATEGORIES['Z'].code_points + - UNICODE_CATEGORIES['C'].code_points, key=lambda x: x if isinstance(x, int) else x[0] + UNICODE_CATEGORIES['P'].code_points + UNICODE_CATEGORIES['Z'].code_points + UNICODE_CATEGORIES['C'].code_points, + key=lambda x: x if isinstance(x, int) else x[0] ) # Single and Multi character escapes @@ -312,9 +312,9 @@ def get_python_regex(xml_regex): elif ch in ('?', '+', '*'): if pos == 0: raise XMLSchemaRegexError("unexpected quantifier %r at position %d: %r" % (ch, pos, xml_regex)) - elif pos < xml_regex_len - 1 and xml_regex[pos+1] in ('?', '+', '*', '{'): + elif pos < xml_regex_len - 1 and xml_regex[pos + 1] in ('?', '+', '*', '{'): raise XMLSchemaRegexError( - "unexpected meta character %r at position %d: %r" % (xml_regex[pos+1], pos+1, xml_regex) + "unexpected meta character %r at position %d: %r" % (xml_regex[pos + 1], pos + 1, xml_regex) ) regex.append(ch) elif ch == '\\': diff --git a/xmlschema/tests/__init__.py b/xmlschema/tests/__init__.py index de59196..158459b 100644 --- a/xmlschema/tests/__init__.py +++ b/xmlschema/tests/__init__.py @@ -64,7 +64,7 @@ class XMLSchemaTestCase(unittest.TestCase): etree_register_namespace(prefix='', uri=XSD_NAMESPACE) etree_register_namespace(prefix='ns', uri="ns") SCHEMA_TEMPLATE = """ - {1} """ @@ -131,7 +131,7 @@ class XMLSchemaTestCase(unittest.TestCase): root = etree_element('schema', attrib={ 'xmlns:ns': "ns", 'xmlns': "http://www.w3.org/2001/XMLSchema", - 'targetNamespace': "ns", + 'targetNamespace': "ns", 'elementFormDefault': "qualified", 'version': self.schema_class.XSD_VERSION, }) diff --git a/xmlschema/tests/test_cases/issues/issue_026/issue_026.xsd b/xmlschema/tests/test_cases/issues/issue_026/issue_026.xsd index a9eef8e..7a85be4 100644 --- a/xmlschema/tests/test_cases/issues/issue_026/issue_026.xsd +++ b/xmlschema/tests/test_cases/issues/issue_026/issue_026.xsd @@ -15,6 +15,6 @@ - + \ No newline at end of file diff --git a/xmlschema/tests/test_models.py b/xmlschema/tests/test_models.py index 8aded08..901ea3a 100644 --- a/xmlschema/tests/test_models.py +++ b/xmlschema/tests/test_models.py @@ -270,7 +270,7 @@ class TestModelValidation(XMLSchemaTestCase): self.assertEqual(model.element, group[2]) self.check_advance_false(model) # don't match self.assertEqual(model.element, group[3]) - self.check_advance_false(model, [(group, 0, group[0][0][:]+group[1:])]) # don't match + self.check_advance_false(model, [(group, 0, group[0][0][:] + group[1:])]) # don't match model.restart() self.assertEqual(model.element, group[0][0][0]) diff --git a/xmlschema/tests/test_package.py b/xmlschema/tests/test_package.py index f3a2c47..619164a 100644 --- a/xmlschema/tests/test_package.py +++ b/xmlschema/tests/test_package.py @@ -121,11 +121,8 @@ class TestPackaging(unittest.TestCase): message = "\nFound a debug missing statement at line %d or file %r: %r" filename = None file_excluded = [] - files = ( - glob.glob(os.path.join(self.source_dir, '*.py')) + + files = glob.glob(os.path.join(self.source_dir, '*.py')) + \ glob.glob(os.path.join(self.source_dir, 'validators/*.py')) - ) - for line in fileinput.input(files): if fileinput.isfirstline(): filename = fileinput.filename() diff --git a/xmlschema/tests/test_schemas.py b/xmlschema/tests/test_schemas.py index ac4ab13..2f86602 100644 --- a/xmlschema/tests/test_schemas.py +++ b/xmlschema/tests/test_schemas.py @@ -55,11 +55,11 @@ class TestXMLSchema10(XMLSchemaTestCase): source = """ {0} - + <{1}Content> - - {2} + + {2} @@ -95,7 +95,6 @@ class TestXMLSchema10(XMLSchemaTestCase): - @@ -156,7 +155,6 @@ class TestXMLSchema10(XMLSchemaTestCase): - @@ -360,11 +358,9 @@ class TestXMLSchema10(XMLSchemaTestCase): - - @@ -421,7 +417,7 @@ class TestXMLSchema10(XMLSchemaTestCase): def test_base_schemas(self): from xmlschema.validators.schema import XML_SCHEMA_FILE - schema = self.schema_class(XML_SCHEMA_FILE) + self.schema_class(XML_SCHEMA_FILE) def test_recursive_complex_type(self): schema = self.schema_class(""" @@ -567,7 +563,7 @@ class TestXMLSchema11(TestXMLSchema10): self.assertTrue(xsd_type.is_valid(etree_element('a', attrib={'min': '25', 'max': '100'}))) def test_open_content(self): - schema = self.check_schema(""" + self.check_schema(""" @@ -583,6 +579,7 @@ class TestXMLSchema11(TestXMLSchema10): """) + def make_schema_test_class(test_file, test_args, test_num, schema_class, check_with_lxml): """ Creates a schema test class. @@ -695,7 +692,7 @@ def make_schema_test_class(test_file, test_args, test_num, schema_class, check_w # Check with lxml.etree.XMLSchema class if check_with_lxml and lxml_etree is not None: - self.check_lxml_schema(xmlschema_time=time.time()-start_time) + self.check_lxml_schema(xmlschema_time=time.time() - start_time) self.check_errors(xsd_file, expected_errors) TestSchema.__name__ = TestSchema.__qualname__ = str('TestSchema{0:03}'.format(test_num)) diff --git a/xmlschema/tests/test_validators.py b/xmlschema/tests/test_validators.py index 170bc7e..472221a 100644 --- a/xmlschema/tests/test_validators.py +++ b/xmlschema/tests/test_validators.py @@ -24,7 +24,7 @@ from elementpath import datatypes import xmlschema from xmlschema import ( - XMLSchemaEncodeError, XMLSchemaValidationError, XMLSchema, ParkerConverter, + XMLSchemaEncodeError, XMLSchemaValidationError, ParkerConverter, BadgerFishConverter, AbderaConverter, JsonMLConverter ) from xmlschema.compat import unicode_type, ordered_dict_class @@ -56,11 +56,11 @@ _VEHICLES_DICT_ALT = [ {'vh:cars': [ {'vh:car': None, '@make': 'Porsche', '@model': '911'}, {'vh:car': None, '@make': 'Porsche', '@model': '911'} - ]}, + ]}, {'vh:bikes': [ {'vh:bike': None, '@make': 'Harley-Davidson', '@model': 'WL'}, {'vh:bike': None, '@make': 'Yamaha', '@model': 'XS650'} - ]}, + ]}, {'@xsi:schemaLocation': 'http://example.com/vehicles vehicles.xsd'} ] @@ -95,7 +95,7 @@ _COLLECTION_DICT = { 'position': 2, 'title': None, 'year': '1925' - }] + }] } _COLLECTION_PARKER = { @@ -165,7 +165,7 @@ _COLLECTION_BADGERFISH = { 'position': {'$': 2}, 'title': {}, 'year': {'$': '1925'} - }] + }] } } @@ -624,7 +624,7 @@ class TestValidation11(TestValidation): " beta" "")) self.assertFalse(xs.is_valid("" - " alpha" # Misses required attribute + " alpha" # Misses required attribute " beta" "")) @@ -912,6 +912,47 @@ class TestDecoding(XMLSchemaTestCase): self.check_decode(decimal_or_nan, '95.0', Decimal('95.0')) self.check_decode(decimal_or_nan, 'NaN', u'NaN') + def test_default_values(self): + # From issue #108 + xsd_text = """ + + + + + + + + + + + + + """ + + schema = self.schema_class(xsd_text) + self.assertEqual(schema.to_dict("text"), + {'@attrWithDefault': 'default_value', + '@attrWithFixed': 'fixed_value', + '$': 'text'}) + self.assertEqual(schema.to_dict(""), + {'@attrWithDefault': 'default_value', + '@attrWithFixed': 'fixed_value', + '$': 'default_value'}) + self.assertEqual(schema.to_dict("""text"""), + {'$': 'text', + '@attr': 'attr_value', + '@attrWithDefault': 'default_value', + '@attrWithFixed': 'fixed_value'}) + + self.assertEqual(schema.to_dict("text", use_defaults=False), + {'@attrWithFixed': 'fixed_value', '$': 'text'}) + self.assertEqual(schema.to_dict("""text""", use_defaults=False), + {'$': 'text', '@attr': 'attr_value', '@attrWithFixed': 'fixed_value'}) + self.assertEqual(schema.to_dict("", use_defaults=False), {'@attrWithFixed': 'fixed_value'}) + + self.assertEqual(schema.to_dict(""), 'default_value') + self.assertIsNone(schema.to_dict("", use_defaults=False)) + class TestDecoding11(TestDecoding): schema_class = XMLSchema11 @@ -1112,7 +1153,7 @@ class TestEncoding(XMLSchemaTestCase): - + @@ -1142,7 +1183,7 @@ class TestEncoding(XMLSchemaTestCase): - + """) diff --git a/xmlschema/validators/attributes.py b/xmlschema/validators/attributes.py index 41a21eb..f148e0a 100644 --- a/xmlschema/validators/attributes.py +++ b/xmlschema/validators/attributes.py @@ -230,7 +230,7 @@ class XsdAttribute(XsdComponent, ValidationMixin): yield obj def iter_decode(self, text, validation='lax', **kwargs): - if not text and kwargs.get('use_defaults', True) and self.default is not None: + if not text and self.default is not None: text = self.default if self.fixed is not None and text != self.fixed and validation != 'skip': yield self.validation_error(validation, "value differs from fixed value", text, **kwargs) @@ -297,7 +297,7 @@ class Xsd11Attribute(XsdAttribute): class XsdAttributeGroup(MutableMapping, XsdComponent, ValidationMixin): """ Class for XSD 'attributeGroup' definitions. - + - Content: (annotation?, (simpleContent | complexContent | + Content: (annotation?, (simpleContent | complexContent | ((group | all | choice | sequence)?, ((attribute | attributeGroup)*, anyAttribute?)))) """ @@ -627,7 +627,7 @@ class Xsd11ComplexType(XsdComplexType): name = NCName defaultAttributesApply = boolean : true {any attributes with non-schema namespace . . .}> - Content: (annotation?, (simpleContent | complexContent | (openContent?, + Content: (annotation?, (simpleContent | complexContent | (openContent?, (group | all | choice | sequence)?, ((attribute | attributeGroup)*, anyAttribute?), assert*))) """ diff --git a/xmlschema/validators/elements.py b/xmlschema/validators/elements.py index 98f0bad..50fd0f7 100644 --- a/xmlschema/validators/elements.py +++ b/xmlschema/validators/elements.py @@ -40,7 +40,7 @@ XSD_ATTRIBUTE_GROUP_ELEMENT = etree_element(XSD_ATTRIBUTE_GROUP) class XsdElement(XsdComponent, ValidationMixin, ParticleMixin, ElementPathMixin): """ Class for XSD 1.0 'element' declarations. - + # """ -This module contains functions and classes for managing namespaces's -XSD declarations/definitions. +This module contains functions and classes for namespaces XSD declarations/definitions. """ from __future__ import unicode_literals import re diff --git a/xmlschema/validators/groups.py b/xmlschema/validators/groups.py index 287fa24..2242409 100644 --- a/xmlschema/validators/groups.py +++ b/xmlschema/validators/groups.py @@ -503,7 +503,7 @@ class XsdGroup(XsdComponent, ModelGroup, ValidationMixin): if depth <= MAX_MODEL_DEPTH: for item in self: if isinstance(item, XsdGroup): - for e in item.iter_elements(depth+1): + for e in item.iter_elements(depth + 1): yield e else: yield item diff --git a/xmlschema/validators/identities.py b/xmlschema/validators/identities.py index e026c43..8c1cd7a 100644 --- a/xmlschema/validators/identities.py +++ b/xmlschema/validators/identities.py @@ -204,8 +204,8 @@ class XsdIdentity(XsdComponent): values[v] += 1 for value, count in values.items(): - if count > 1: - yield XMLSchemaValidationError(self, elem, reason="duplicated value %r." % value) + if value and count > 1: + yield XMLSchemaValidationError(self, elem, reason="duplicated value {!r}.".format(value)) class XsdUnique(XsdIdentity): @@ -300,6 +300,6 @@ class XsdKeyref(XsdIdentity): continue if v not in refer_values: - reason = "Key %r with value %r not found for identity constraint of element %r." \ - % (self.prefixed_name, v, qname_to_prefixed(elem.tag, self.namespaces)) + reason = "Key {!r} with value {!r} not found for identity constraint of element {!r}." \ + .format(self.prefixed_name, v, qname_to_prefixed(elem.tag, self.namespaces)) yield XMLSchemaValidationError(validator=self, obj=elem, reason=reason) diff --git a/xmlschema/validators/models.py b/xmlschema/validators/models.py index e9381a8..40dec63 100644 --- a/xmlschema/validators/models.py +++ b/xmlschema/validators/models.py @@ -155,7 +155,7 @@ class ModelGroup(MutableSequence, ParticleMixin): elif not item.is_pointless(parent=self): yield item else: - for obj in item.iter_model(depth+1): + for obj in item.iter_model(depth + 1): yield obj def iter_elements(self, depth=0): @@ -169,7 +169,7 @@ class ModelGroup(MutableSequence, ParticleMixin): raise XMLSchemaModelDepthError(self) for item in self: if isinstance(item, ModelGroup): - for e in item.iter_elements(depth+1): + for e in item.iter_elements(depth + 1): yield e else: yield item @@ -178,7 +178,7 @@ class ModelGroup(MutableSequence, ParticleMixin): if depth <= MAX_MODEL_DEPTH: for item in self: if isinstance(item, ModelGroup): - for e in item.iter_subelements(depth+1): + for e in item.iter_subelements(depth + 1): yield e else: yield item @@ -269,8 +269,8 @@ def distinguishable_paths(path1, path2): if path1[depth].model != 'sequence': return before1 and before2 or \ - (before1 and (univocal1 and e1.is_univocal() or after1 or path1[depth].max_occurs == 1)) or \ - (before2 and (univocal2 and e2.is_univocal() or after2 or path2[depth].max_occurs == 1)) + (before1 and (univocal1 and e1.is_univocal() or after1 or path1[depth].max_occurs == 1)) or \ + (before2 and (univocal2 and e2.is_univocal() or after2 or path2[depth].max_occurs == 1)) elif path1[depth].max_occurs == 1: return before2 or (before1 or univocal1) and (e1.is_univocal() or after1) else: diff --git a/xmlschema/validators/simple_types.py b/xmlschema/validators/simple_types.py index 2b0d59c..3f5dc97 100644 --- a/xmlschema/validators/simple_types.py +++ b/xmlschema/validators/simple_types.py @@ -374,8 +374,8 @@ class XsdSimpleType(XsdType, ValidationMixin): # simpleType's derived classes: class XsdAtomic(XsdSimpleType): """ - Class for atomic simpleType definitions. An atomic definition has - a base_type attribute that refers to primitive or derived atomic + Class for atomic simpleType definitions. An atomic definition has + a base_type attribute that refers to primitive or derived atomic built-in type or another derived simpleType. """ _special_types = {XSD_ANY_TYPE, XSD_ANY_SIMPLE_TYPE, XSD_ANY_ATOMIC_TYPE} @@ -599,9 +599,9 @@ class XsdAtomicBuiltin(XsdAtomic): class XsdList(XsdSimpleType): """ - Class for 'list' definitions. A list definition has an item_type attribute + Class for 'list' definitions. A list definition has an item_type attribute that refers to an atomic or union simpleType definition. - + - Content: (annotation?, (simpleType?, (minExclusive | minInclusive | maxExclusive | - maxInclusive | totalDigits | fractionDigits | length | minLength | maxLength | + Content: (annotation?, (simpleType?, (minExclusive | minInclusive | maxExclusive | + maxInclusive | totalDigits | fractionDigits | length | minLength | maxLength | enumeration | whiteSpace | pattern)*)) """ @@ -1269,9 +1269,9 @@ class Xsd11AtomicRestriction(XsdAtomicRestriction): base = QName id = ID {any attributes with non-schema namespace . . .}> - Content: (annotation?, (simpleType?, (minExclusive | minInclusive | maxExclusive | - maxInclusive | totalDigits | fractionDigits | length | minLength | maxLength | - enumeration | whiteSpace | pattern | assertion | explicitTimezone | + Content: (annotation?, (simpleType?, (minExclusive | minInclusive | maxExclusive | + maxInclusive | totalDigits | fractionDigits | length | minLength | maxLength | + enumeration | whiteSpace | pattern | assertion | explicitTimezone | {any with namespace: ##other})*)) """ diff --git a/xmlschema/validators/wildcards.py b/xmlschema/validators/wildcards.py index 584ead1..bc221f8 100644 --- a/xmlschema/validators/wildcards.py +++ b/xmlschema/validators/wildcards.py @@ -25,6 +25,9 @@ from .xsdbase import ValidationMixin, XsdComponent, ParticleMixin class XsdWildcard(XsdComponent, ValidationMixin): names = {} + namespace = '##any' + not_namespace = () + not_qname = () def __init__(self, elem, schema, parent): if parent is None: @@ -40,15 +43,15 @@ class XsdWildcard(XsdComponent, ValidationMixin): super(XsdWildcard, self)._parse() # Parse namespace and processContents - namespace = self.elem.get('namespace', '##any') - items = namespace.strip().split() - if len(items) == 1 and items[0] in ('##any', '##other', '##local', '##targetNamespace'): - self.namespace = namespace.strip() - elif not all(not s.startswith('##') or s in {'##local', '##targetNamespace'} for s in items): + namespace = self.elem.get('namespace', '##any').strip() + if namespace == '##any': + pass + elif namespace in {'##other', '##local', '##targetNamespace'}: + self.namespace = namespace + elif not all(not s.startswith('##') or s in {'##local', '##targetNamespace'} for s in namespace.split()): self.parse_error("wrong value %r for 'namespace' attribute." % namespace) - self.namespace = '##any' else: - self.namespace = namespace.strip() + self.namespace = namespace self.process_contents = self.elem.get('processContents', 'strict') if self.process_contents not in {'lax', 'skip', 'strict'}: @@ -99,7 +102,15 @@ class XsdWildcard(XsdComponent, ValidationMixin): return self.is_namespace_allowed(default_namespace) def is_namespace_allowed(self, namespace): - if self.namespace == '##any' or namespace == XSI_NAMESPACE: + if self.not_namespace: + if '##local' in self.not_namespace and namespace == '': + return False + elif '##targetNamespace' in self.not_namespace and namespace == self.target_namespace: + return False + else: + return namespace not in self.not_namespace + + elif self.namespace == '##any' or namespace == XSI_NAMESPACE: return True elif self.namespace == '##other': if namespace: @@ -261,7 +272,7 @@ class XsdAnyElement(XsdWildcard, ParticleMixin, ElementPathMixin): class XsdAnyAttribute(XsdWildcard): """ Class for XSD 1.0 'anyAttribute' wildcards. - + """ - pass + def _parse(self): + super(Xsd11AnyElement, self)._parse() + + # Parse notNamespace attribute + try: + not_namespace = self.elem.attrib['notNamespace'].strip().split() + except KeyError: + pass + else: + if 'namespace' in self.elem.attrib: + self.parse_error("'namespace' and 'notNamespace' attributes are mutually exclusive.") + elif not all(not s.startswith('##') or s in {'##local', '##targetNamespace'} for s in not_namespace): + self.parse_error("wrong value %r for 'notNamespace' attribute." % self.elem.attrib['notNamespace']) + else: + self.not_namespace = not_namespace + + # Parse notQName attribute + try: + not_qname = self.elem.attrib['notQName'].strip().split() + except KeyError: + pass + else: + if not all(not s.startswith('##') or s in {'##defined', '##definedSibling'} for s in not_qname): + self.parse_error("wrong value %r for 'notQName' attribute." % self.elem.attrib['notQName']) + else: + self.not_qname = not_qname class Xsd11AnyAttribute(XsdAnyAttribute): @@ -432,7 +420,32 @@ class Xsd11AnyAttribute(XsdAnyAttribute): Content: (annotation?) """ - pass + def _parse(self): + super(Xsd11AnyAttribute, self)._parse() + + # Parse notNamespace attribute + try: + not_namespace = self.elem.attrib['notNamespace'].strip().split() + except KeyError: + pass + else: + if 'namespace' in self.elem.attrib: + self.parse_error("'namespace' and 'notNamespace' attributes are mutually exclusive.") + elif not all(not s.startswith('##') or s in {'##local', '##targetNamespace'} for s in not_namespace): + self.parse_error("wrong value %r for 'notNamespace' attribute." % self.elem.attrib['notNamespace']) + else: + self.not_namespace = not_namespace + + # Parse notQName attribute + try: + not_qname = self.elem.attrib['notQName'].strip().split() + except KeyError: + pass + else: + if not all(not s.startswith('##') or s == '##defined' for s in not_qname): + self.parse_error("wrong value %r for 'notQName' attribute." % self.elem.attrib['notQName']) + else: + self.not_qname = not_qname class XsdOpenContent(XsdComponent): @@ -467,6 +480,10 @@ class XsdOpenContent(XsdComponent): if child is not None and child.tag == XSD_ANY: self.any_element = Xsd11AnyElement(child, self.schema, self) + @property + def built(self): + return True + class XsdDefaultOpenContent(XsdOpenContent): """