739 lines
33 KiB
Python
739 lines
33 KiB
Python
#!/usr/bin/env 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>
|
|
#
|
|
import unittest
|
|
import os
|
|
from decimal import Decimal
|
|
import base64
|
|
from elementpath import datatypes
|
|
|
|
import xmlschema
|
|
from xmlschema import XMLSchemaValidationError, ParkerConverter, BadgerFishConverter, \
|
|
AbderaConverter, JsonMLConverter
|
|
|
|
from xmlschema.converters import UnorderedConverter
|
|
from xmlschema.compat import unicode_type, ordered_dict_class
|
|
from xmlschema.etree import ElementTree, lxml_etree
|
|
from xmlschema.tests import XsdValidatorTestCase
|
|
from xmlschema.validators import XMLSchema11
|
|
|
|
VEHICLES_DICT = {
|
|
'@xmlns:vh': 'http://example.com/vehicles',
|
|
'@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
|
|
'@xsi:schemaLocation': 'http://example.com/vehicles vehicles.xsd',
|
|
'vh:cars': {
|
|
'vh:car': [
|
|
{'@make': 'Porsche', '@model': '911'},
|
|
{'@make': 'Porsche', '@model': '911'}
|
|
]},
|
|
'vh:bikes': {
|
|
'vh:bike': [
|
|
{'@make': 'Harley-Davidson', '@model': 'WL'},
|
|
{'@make': 'Yamaha', '@model': 'XS650'}
|
|
]}
|
|
}
|
|
|
|
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'}
|
|
]
|
|
|
|
COLLECTION_DICT = {
|
|
'@xmlns:col': 'http://example.com/ns/collection',
|
|
'@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
|
|
'@xsi:schemaLocation': 'http://example.com/ns/collection collection.xsd',
|
|
'object': [{
|
|
'@available': True,
|
|
'@id': 'b0836217462',
|
|
'author': {
|
|
'@id': 'PAR',
|
|
'born': '1841-02-25',
|
|
'dead': '1919-12-03',
|
|
'name': 'Pierre-Auguste Renoir',
|
|
'qualification': 'painter'
|
|
},
|
|
'estimation': Decimal('10000.00'),
|
|
'position': 1,
|
|
'title': 'The Umbrellas',
|
|
'year': '1886'},
|
|
{
|
|
'@available': True,
|
|
'@id': 'b0836217463',
|
|
'author': {
|
|
'@id': 'JM',
|
|
'born': '1893-04-20',
|
|
'dead': '1983-12-25',
|
|
'name': u'Joan Miró',
|
|
'qualification': 'painter, sculptor and ceramicist'
|
|
},
|
|
'position': 2,
|
|
'title': None,
|
|
'year': '1925'
|
|
}]
|
|
}
|
|
|
|
COLLECTION_PARKER = {
|
|
'object': [{'author': {'born': '1841-02-25',
|
|
'dead': '1919-12-03',
|
|
'name': 'Pierre-Auguste Renoir',
|
|
'qualification': 'painter'},
|
|
'estimation': 10000.0,
|
|
'position': 1,
|
|
'title': 'The Umbrellas',
|
|
'year': '1886'},
|
|
{'author': {'born': '1893-04-20',
|
|
'dead': '1983-12-25',
|
|
'name': u'Joan Miró',
|
|
'qualification': 'painter, sculptor and ceramicist'},
|
|
'position': 2,
|
|
'title': None,
|
|
'year': '1925'}]}
|
|
|
|
COLLECTION_PARKER_ROOT = {
|
|
'col:collection': {'object': [{'author': {'born': '1841-02-25',
|
|
'dead': '1919-12-03',
|
|
'name': 'Pierre-Auguste Renoir',
|
|
'qualification': 'painter'},
|
|
'estimation': 10000.0,
|
|
'position': 1,
|
|
'title': 'The Umbrellas',
|
|
'year': '1886'},
|
|
{'author': {'born': '1893-04-20',
|
|
'dead': '1983-12-25',
|
|
'name': u'Joan Miró',
|
|
'qualification': 'painter, sculptor and ceramicist'},
|
|
'position': 2,
|
|
'title': None,
|
|
'year': '1925'}]}}
|
|
|
|
COLLECTION_BADGERFISH = {
|
|
'@xmlns': {
|
|
'col': 'http://example.com/ns/collection',
|
|
'xsi': 'http://www.w3.org/2001/XMLSchema-instance'},
|
|
'col:collection': {
|
|
'@xsi:schemaLocation': 'http://example.com/ns/collection collection.xsd',
|
|
'object': [{
|
|
'@available': True,
|
|
'@id': 'b0836217462',
|
|
'author': {
|
|
'@id': 'PAR',
|
|
'born': {'$': '1841-02-25'},
|
|
'dead': {'$': '1919-12-03'},
|
|
'name': {'$': 'Pierre-Auguste Renoir'},
|
|
'qualification': {'$': 'painter'}},
|
|
'estimation': {'$': 10000.0},
|
|
'position': {'$': 1},
|
|
'title': {'$': 'The Umbrellas'},
|
|
'year': {'$': '1886'}},
|
|
{
|
|
'@available': True,
|
|
'@id': 'b0836217463',
|
|
'author': {
|
|
'@id': 'JM',
|
|
'born': {'$': '1893-04-20'},
|
|
'dead': {'$': '1983-12-25'},
|
|
'name': {'$': u'Joan Miró'},
|
|
'qualification': {
|
|
'$': 'painter, sculptor and ceramicist'}
|
|
},
|
|
'position': {'$': 2},
|
|
'title': {},
|
|
'year': {'$': '1925'}
|
|
}]
|
|
}
|
|
}
|
|
|
|
COLLECTION_ABDERA = {
|
|
'attributes': {
|
|
'xsi:schemaLocation': 'http://example.com/ns/collection collection.xsd'
|
|
},
|
|
'children': [
|
|
{
|
|
'object': [
|
|
{
|
|
'attributes': {'available': True, 'id': 'b0836217462'},
|
|
'children': [{
|
|
'author': {
|
|
'attributes': {'id': 'PAR'},
|
|
'children': [{
|
|
'born': '1841-02-25',
|
|
'dead': '1919-12-03',
|
|
'name': 'Pierre-Auguste Renoir',
|
|
'qualification': 'painter'}
|
|
]},
|
|
'estimation': 10000.0,
|
|
'position': 1,
|
|
'title': 'The Umbrellas',
|
|
'year': '1886'}
|
|
]},
|
|
{
|
|
'attributes': {'available': True, 'id': 'b0836217463'},
|
|
'children': [{
|
|
'author': {
|
|
'attributes': {'id': 'JM'},
|
|
'children': [{
|
|
'born': '1893-04-20',
|
|
'dead': '1983-12-25',
|
|
'name': u'Joan Miró',
|
|
'qualification': 'painter, sculptor and ceramicist'}
|
|
]},
|
|
'position': 2,
|
|
'title': [],
|
|
'year': '1925'
|
|
}]
|
|
}]
|
|
}
|
|
]}
|
|
|
|
COLLECTION_JSON_ML = [
|
|
'col:collection',
|
|
{'xmlns:col': 'http://example.com/ns/collection',
|
|
'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
|
|
'xsi:schemaLocation': 'http://example.com/ns/collection collection.xsd'},
|
|
['object',
|
|
{'available': True, 'id': 'b0836217462'},
|
|
['position', 1],
|
|
['title', 'The Umbrellas'],
|
|
['year', '1886'],
|
|
[
|
|
'author',
|
|
{'id': 'PAR'},
|
|
['name', 'Pierre-Auguste Renoir'],
|
|
['born', '1841-02-25'],
|
|
['dead', '1919-12-03'],
|
|
['qualification', 'painter']
|
|
],
|
|
[
|
|
'estimation',
|
|
Decimal('10000.00')
|
|
]],
|
|
['object',
|
|
{'available': True, 'id': 'b0836217463'},
|
|
['position', 2],
|
|
['title'],
|
|
['year', '1925'],
|
|
[
|
|
'author',
|
|
{'id': 'JM'},
|
|
['name', u'Joan Miró'],
|
|
['born', '1893-04-20'],
|
|
['dead', '1983-12-25'],
|
|
['qualification', 'painter, sculptor and ceramicist']
|
|
]]
|
|
]
|
|
|
|
DATA_DICT = {
|
|
'@xmlns:ns': 'ns',
|
|
'@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
|
|
'@xsi:schemaLocation': 'ns ./simple-types.xsd',
|
|
'certification': [
|
|
{'$': 'ISO-9001', '@Year': 1999},
|
|
{'$': 'ISO-27001', '@Year': 2009}
|
|
],
|
|
'decimal_value': [Decimal('1')],
|
|
u'menù': u'baccalà mantecato',
|
|
u'complex_boolean': [
|
|
{'$': True, '@Type': 2}, {'$': False, '@Type': 1}, True, False
|
|
],
|
|
u'simple_boolean': [True, False]
|
|
}
|
|
|
|
|
|
class TestDecoding(XsdValidatorTestCase):
|
|
|
|
def check_decode(self, xsd_component, data, expected, **kwargs):
|
|
if isinstance(expected, type) and issubclass(expected, Exception):
|
|
self.assertRaises(expected, xsd_component.decode, data, **kwargs)
|
|
else:
|
|
obj = xsd_component.decode(data, **kwargs)
|
|
if isinstance(obj, tuple) and len(obj) == 2 and isinstance(obj[1], list) \
|
|
and isinstance(obj[1][0], Exception):
|
|
self.assertEqual(expected, obj[0])
|
|
self.assertTrue(isinstance(obj[0], type(expected)))
|
|
else:
|
|
self.assertEqual(expected, obj)
|
|
self.assertTrue(isinstance(obj, type(expected)))
|
|
|
|
@unittest.skipIf(lxml_etree is None, "The lxml library is not available.")
|
|
def test_lxml(self):
|
|
vh_xml_tree = lxml_etree.parse(self.vh_xml_file)
|
|
self.assertEqual(self.vh_schema.to_dict(vh_xml_tree), VEHICLES_DICT)
|
|
self.assertEqual(xmlschema.to_dict(vh_xml_tree, self.vh_schema.url), VEHICLES_DICT)
|
|
|
|
def test_to_dict_from_etree(self):
|
|
vh_xml_tree = ElementTree.parse(self.vh_xml_file)
|
|
col_xml_tree = ElementTree.parse(self.col_xml_file)
|
|
|
|
xml_dict = self.vh_schema.to_dict(vh_xml_tree)
|
|
self.assertNotEqual(xml_dict, VEHICLES_DICT)
|
|
|
|
xml_dict = self.vh_schema.to_dict(vh_xml_tree, namespaces=self.vh_namespaces)
|
|
self.assertEqual(xml_dict, VEHICLES_DICT)
|
|
|
|
xml_dict = xmlschema.to_dict(vh_xml_tree, self.vh_schema.url, namespaces=self.vh_namespaces)
|
|
self.assertEqual(xml_dict, VEHICLES_DICT)
|
|
|
|
xml_dict = self.col_schema.to_dict(col_xml_tree)
|
|
self.assertNotEqual(xml_dict, COLLECTION_DICT)
|
|
|
|
xml_dict = self.col_schema.to_dict(col_xml_tree, namespaces=self.col_namespaces)
|
|
self.assertEqual(xml_dict, COLLECTION_DICT)
|
|
|
|
xml_dict = xmlschema.to_dict(col_xml_tree, self.col_schema.url, namespaces=self.col_namespaces)
|
|
self.assertEqual(xml_dict, COLLECTION_DICT)
|
|
|
|
def test_to_dict_from_string(self):
|
|
with open(self.vh_xml_file) as f:
|
|
vh_xml_string = f.read()
|
|
|
|
with open(self.col_xml_file) as f:
|
|
col_xml_string = f.read()
|
|
|
|
xml_dict = self.vh_schema.to_dict(vh_xml_string, namespaces=self.vh_namespaces)
|
|
self.assertEqual(xml_dict, VEHICLES_DICT)
|
|
|
|
xml_dict = xmlschema.to_dict(vh_xml_string, self.vh_schema.url, namespaces=self.vh_namespaces)
|
|
self.assertEqual(xml_dict, VEHICLES_DICT)
|
|
|
|
xml_dict = self.col_schema.to_dict(col_xml_string, namespaces=self.col_namespaces)
|
|
self.assertTrue(xml_dict, COLLECTION_DICT)
|
|
|
|
xml_dict = xmlschema.to_dict(col_xml_string, self.col_schema.url, namespaces=self.col_namespaces)
|
|
self.assertTrue(xml_dict, COLLECTION_DICT)
|
|
|
|
def test_date_decoding(self):
|
|
# Issue #136
|
|
schema = xmlschema.XMLSchema("""<?xml version="1.0" encoding="utf-8"?>
|
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" version="1.0">
|
|
<xs:element name="Date">
|
|
<xs:simpleType>
|
|
<xs:restriction base="xs:date">
|
|
<xs:minInclusive value="2000-01-01"/>
|
|
<xs:maxInclusive value="2099-12-31"/>
|
|
</xs:restriction>
|
|
</xs:simpleType>
|
|
</xs:element>
|
|
</xs:schema>""")
|
|
|
|
self.assertEqual(schema.to_dict("<Date>2019-01-01</Date>"), '2019-01-01')
|
|
self.assertEqual(schema.to_dict("<Date>2019-01-01</Date>", datetime_types=True),
|
|
datatypes.Date10.fromstring('2019-01-01'))
|
|
|
|
data, errors = schema.to_dict("<Date>2019-01-01</Date>", validation='lax')
|
|
self.assertEqual(data, '2019-01-01')
|
|
self.assertEqual(errors, [])
|
|
|
|
data, errors = schema.to_dict("<Date>2019-01-01</Date>", validation='lax', datetime_types=True)
|
|
self.assertEqual(data, datatypes.Date10.fromstring('2019-01-01'))
|
|
self.assertEqual(errors, [])
|
|
|
|
data, errors = schema.to_dict("<Date>1999-12-31</Date>", validation='lax')
|
|
self.assertEqual(data, '1999-12-31')
|
|
self.assertEqual(len(errors), 1)
|
|
self.assertIn('value has to be greater or equal than', unicode_type(errors[0]))
|
|
|
|
data, errors = schema.to_dict("<Date>1999-12-31</Date>", validation='lax', datetime_types=True)
|
|
self.assertEqual(data, datatypes.Date10.fromstring('1999-12-31'))
|
|
self.assertEqual(len(errors), 1)
|
|
|
|
data, errors = schema.to_dict("<Date>2019</Date>", validation='lax')
|
|
self.assertIsNone(data)
|
|
self.assertEqual(len(errors), 1)
|
|
|
|
with self.assertRaises(XMLSchemaValidationError):
|
|
schema.to_dict("<Date>2019</Date>")
|
|
|
|
data, errors = schema.to_dict("<Date>2019</Date>", validation='lax')
|
|
self.assertIsNone(data)
|
|
self.assertEqual(len(errors), 1)
|
|
|
|
def test_json_dump_and_load(self):
|
|
vh_xml_tree = ElementTree.parse(self.vh_xml_file)
|
|
col_xml_tree = ElementTree.parse(self.col_xml_file)
|
|
with open(self.vh_json_file, 'w') as f:
|
|
xmlschema.to_json(self.vh_xml_file, f)
|
|
|
|
with open(self.vh_json_file) as f:
|
|
root = xmlschema.from_json(f, self.vh_schema)
|
|
|
|
os.remove(self.vh_json_file)
|
|
self.check_etree_elements(vh_xml_tree, root)
|
|
|
|
with open(self.col_json_file, 'w') as f:
|
|
xmlschema.to_json(self.col_xml_file, f)
|
|
|
|
with open(self.col_json_file) as f:
|
|
root = xmlschema.from_json(f, self.col_schema)
|
|
|
|
os.remove(self.col_json_file)
|
|
self.check_etree_elements(col_xml_tree, root)
|
|
|
|
def test_path(self):
|
|
xt = ElementTree.parse(self.vh_xml_file)
|
|
xd = self.vh_schema.to_dict(xt, '/vh:vehicles/vh:cars', namespaces=self.vh_namespaces)
|
|
self.assertEqual(xd['vh:car'], VEHICLES_DICT['vh:cars']['vh:car'])
|
|
xd = self.vh_schema.to_dict(xt, '/vh:vehicles/vh:bikes', namespaces=self.vh_namespaces)
|
|
self.assertEqual(xd['vh:bike'], VEHICLES_DICT['vh:bikes']['vh:bike'])
|
|
|
|
def test_validation_strict(self):
|
|
self.assertRaises(
|
|
xmlschema.XMLSchemaValidationError,
|
|
self.vh_schema.to_dict,
|
|
ElementTree.parse(self.casepath('examples/vehicles/vehicles-2_errors.xml')),
|
|
validation='strict',
|
|
namespaces=self.vh_namespaces
|
|
)
|
|
|
|
def test_validation_skip(self):
|
|
xt = ElementTree.parse(self.casepath('features/decoder/data3.xml'))
|
|
xd = self.st_schema.decode(xt, validation='skip', namespaces={'ns': 'ns'})
|
|
self.assertEqual(xd['decimal_value'], ['abc'])
|
|
|
|
def test_datatypes(self):
|
|
xt = ElementTree.parse(self.casepath('features/decoder/data.xml'))
|
|
xd = self.st_schema.to_dict(xt, namespaces=self.default_namespaces)
|
|
self.assertEqual(xd, DATA_DICT)
|
|
|
|
def test_datetime_types(self):
|
|
xs = self.get_schema('<xs:element name="dt" type="xs:dateTime"/>')
|
|
self.assertEqual(xs.decode('<dt>2019-01-01T13:40:00</dt>'), '2019-01-01T13:40:00')
|
|
self.assertEqual(xs.decode('<dt>2019-01-01T13:40:00</dt>', datetime_types=True),
|
|
datatypes.DateTime10.fromstring('2019-01-01T13:40:00'))
|
|
|
|
xs = self.get_schema('<xs:element name="dt" type="xs:date"/>')
|
|
self.assertEqual(xs.decode('<dt>2001-04-15</dt>'), '2001-04-15')
|
|
self.assertEqual(xs.decode('<dt>2001-04-15</dt>', datetime_types=True),
|
|
datatypes.Date10.fromstring('2001-04-15'))
|
|
|
|
def test_duration_type(self):
|
|
xs = self.get_schema('<xs:element name="td" type="xs:duration"/>')
|
|
self.assertEqual(xs.decode('<td>P5Y3MT60H30.001S</td>'), 'P5Y3MT60H30.001S')
|
|
self.assertEqual(xs.decode('<td>P5Y3MT60H30.001S</td>', datetime_types=True),
|
|
datatypes.Duration.fromstring('P5Y3M2DT12H30.001S'))
|
|
|
|
def test_default_converter(self):
|
|
self.assertEqual(self.col_schema.to_dict(self.col_xml_file), COLLECTION_DICT)
|
|
|
|
default_dict = self.col_schema.to_dict(self.col_xml_file, converter=xmlschema.XMLSchemaConverter)
|
|
self.assertEqual(default_dict, COLLECTION_DICT)
|
|
|
|
default_dict_root = self.col_schema.to_dict(self.col_xml_file, preserve_root=True)
|
|
self.assertEqual(default_dict_root, {'col:collection': COLLECTION_DICT})
|
|
|
|
def test_visitor_converter(self):
|
|
visitor_dict = self.col_schema.to_dict(self.col_xml_file, converter=UnorderedConverter)
|
|
self.assertEqual(visitor_dict, COLLECTION_DICT)
|
|
|
|
visitor_dict_root = self.col_schema.to_dict(
|
|
self.col_xml_file, converter=UnorderedConverter(preserve_root=True))
|
|
self.assertEqual(visitor_dict_root, {'col:collection': COLLECTION_DICT})
|
|
|
|
def test_parker_converter(self):
|
|
parker_dict = self.col_schema.to_dict(self.col_xml_file, converter=xmlschema.ParkerConverter)
|
|
self.assertEqual(parker_dict, COLLECTION_PARKER)
|
|
|
|
parker_dict_root = self.col_schema.to_dict(
|
|
self.col_xml_file, converter=ParkerConverter(preserve_root=True), decimal_type=float)
|
|
self.assertEqual(parker_dict_root, COLLECTION_PARKER_ROOT)
|
|
|
|
def test_badgerfish_converter(self):
|
|
badgerfish_dict = self.col_schema.to_dict(
|
|
self.col_xml_file, converter=BadgerFishConverter, decimal_type=float)
|
|
self.assertEqual(badgerfish_dict, COLLECTION_BADGERFISH)
|
|
|
|
def test_abdera_converter(self):
|
|
abdera_dict = self.col_schema.to_dict(
|
|
self.col_xml_file, converter=AbderaConverter, decimal_type=float, dict_class=dict)
|
|
self.assertEqual(abdera_dict, COLLECTION_ABDERA)
|
|
|
|
def test_json_ml_converter(self):
|
|
json_ml_dict = self.col_schema.to_dict(self.col_xml_file, converter=JsonMLConverter)
|
|
self.assertEqual(json_ml_dict, COLLECTION_JSON_ML)
|
|
|
|
def test_dict_granularity(self):
|
|
"""Based on Issue #22, test to make sure an xsd indicating list with
|
|
dictionaries, returns just that even when it has a single dict. """
|
|
xsd_string = self.casepath('issues/issue_022/xsd_string.xsd')
|
|
xml_string_1 = self.casepath('issues/issue_022/xml_string_1.xml')
|
|
xml_string_2 = self.casepath('issues/issue_022/xml_string_2.xml')
|
|
xsd_schema = xmlschema.XMLSchema(xsd_string)
|
|
xml_data_1 = xsd_schema.to_dict(xml_string_1)
|
|
xml_data_2 = xsd_schema.to_dict(xml_string_2)
|
|
self.assertTrue(isinstance(xml_data_1['bar'], type(xml_data_2['bar'])),
|
|
msg="XSD with an array that return a single element from xml must still yield a list.")
|
|
|
|
def test_any_type(self):
|
|
any_type = xmlschema.XMLSchema.meta_schema.types['anyType']
|
|
xml_data_1 = ElementTree.Element('dummy')
|
|
self.assertEqual(any_type.decode(xml_data_1), (None, [], []))
|
|
xml_data_2 = ElementTree.fromstring('<root>\n <child_1/>\n <child_2/>\n</root>')
|
|
self.assertEqual(any_type.decode(xml_data_2), (None, [], [])) # Currently no decoding yet
|
|
|
|
def test_choice_model_decoding(self):
|
|
schema = xmlschema.XMLSchema(self.casepath('issues/issue_041/issue_041.xsd'))
|
|
data = schema.to_dict(self.casepath('issues/issue_041/issue_041.xml'))
|
|
self.assertEqual(data, {
|
|
'@xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
|
|
'@xsi:noNamespaceSchemaLocation': 'issue_041.xsd',
|
|
'Name': 'SomeNameValueThingy',
|
|
'Value': {'Integer': 0}
|
|
})
|
|
|
|
def test_cdata_decoding(self):
|
|
schema = xmlschema.XMLSchema(self.casepath('issues/issue_046/issue_046.xsd'))
|
|
xml_file = self.casepath('issues/issue_046/issue_046.xml')
|
|
self.assertEqual(
|
|
schema.decode(xml_file, dict_class=ordered_dict_class, cdata_prefix='#'),
|
|
ordered_dict_class(
|
|
[('@xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'),
|
|
('@xsi:noNamespaceSchemaLocation', 'issue_046.xsd'),
|
|
('#1', 'Dear Mr.'), ('name', 'John Smith'),
|
|
('#2', '.\n Your order'), ('orderid', 1032),
|
|
('#3', 'will be shipped on'), ('shipdate', '2001-07-13'), ('#4', '.')]
|
|
))
|
|
|
|
def test_string_facets(self):
|
|
none_empty_string_type = self.st_schema.types['none_empty_string']
|
|
self.check_decode(none_empty_string_type, '', XMLSchemaValidationError)
|
|
name_type = self.st_schema.types['NameType']
|
|
self.check_decode(name_type, '', XMLSchemaValidationError)
|
|
|
|
def test_binary_data_facets(self):
|
|
hex_code_type = self.st_schema.types['hexCode']
|
|
self.check_decode(hex_code_type, u'00D7310A', u'00D7310A')
|
|
|
|
base64_code_type = self.st_schema.types['base64Code']
|
|
self.check_decode(base64_code_type, base64.b64encode(b'ok'), XMLSchemaValidationError)
|
|
base64_value = base64.b64encode(b'hello')
|
|
self.check_decode(base64_code_type, base64_value, base64_value.decode('utf-8'))
|
|
self.check_decode(base64_code_type, base64.b64encode(b'abcefgh'), u'YWJjZWZnaA==')
|
|
self.check_decode(base64_code_type, b' Y W J j ZWZ\t\tn\na A= =', u'Y W J j ZWZ n a A= =')
|
|
self.check_decode(base64_code_type, u' Y W J j ZWZ\t\tn\na A= =', u'Y W J j ZWZ n a A= =')
|
|
self.check_decode(base64_code_type, base64.b64encode(b'abcefghi'), u'YWJjZWZnaGk=')
|
|
|
|
self.check_decode(base64_code_type, u'YWJjZWZnaA=', XMLSchemaValidationError)
|
|
self.check_decode(base64_code_type, u'YWJjZWZna$==', XMLSchemaValidationError)
|
|
|
|
base64_length4_type = self.st_schema.types['base64Length4']
|
|
self.check_decode(base64_length4_type, base64.b64encode(b'abc'), XMLSchemaValidationError)
|
|
self.check_decode(base64_length4_type, base64.b64encode(b'abce'), u'YWJjZQ==')
|
|
self.check_decode(base64_length4_type, base64.b64encode(b'abcef'), XMLSchemaValidationError)
|
|
|
|
base64_length5_type = self.st_schema.types['base64Length5']
|
|
self.check_decode(base64_length5_type, base64.b64encode(b'1234'), XMLSchemaValidationError)
|
|
self.check_decode(base64_length5_type, base64.b64encode(b'12345'), u'MTIzNDU=')
|
|
self.check_decode(base64_length5_type, base64.b64encode(b'123456'), XMLSchemaValidationError)
|
|
|
|
def test_decimal_type(self):
|
|
schema = self.get_schema("""
|
|
<xs:element name="A" type="A_type" />
|
|
<xs:simpleType name="A_type">
|
|
<xs:restriction base="xs:decimal">
|
|
<xs:minInclusive value="100.50"/>
|
|
</xs:restriction>
|
|
</xs:simpleType>
|
|
""")
|
|
|
|
self.check_decode(schema, '<A>120.48</A>', Decimal('120.48'))
|
|
self.check_decode(schema, '<A>100.50</A>', Decimal('100.50'), process_namespaces=False)
|
|
self.check_decode(schema, '<A>100.49</A>', XMLSchemaValidationError)
|
|
self.check_decode(schema, '<A>120.48</A>', 120.48, decimal_type=float)
|
|
# Issue #66
|
|
self.check_decode(schema, '<A>120.48</A>', '120.48', decimal_type=str)
|
|
|
|
def test_nillable(self):
|
|
# Issue #76
|
|
xsd_string = """<?xml version="1.0" encoding="UTF-8"?>
|
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
|
|
<xs:element name="foo" type="Foo" />
|
|
<xs:complexType name="Foo">
|
|
<xs:sequence minOccurs="1" maxOccurs="1">
|
|
<xs:element name="bar" type="xs:integer" nillable="true" />
|
|
</xs:sequence>
|
|
</xs:complexType>
|
|
</xs:schema>
|
|
"""
|
|
xsd_schema = xmlschema.XMLSchema(xsd_string)
|
|
xml_string_1 = "<foo><bar>0</bar></foo>"
|
|
xml_string_2 = """<?xml version="1.0" encoding="UTF-8"?>
|
|
<foo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
<bar xsi:nil="true"></bar>
|
|
</foo>
|
|
"""
|
|
self.assertTrue(xsd_schema.is_valid(source=xml_string_1, use_defaults=False))
|
|
self.assertTrue(xsd_schema.is_valid(source=xml_string_2, use_defaults=False))
|
|
obj = xsd_schema.decode(xml_string_2, use_defaults=False)
|
|
self.check_etree_elements(ElementTree.fromstring(xml_string_2), xsd_schema.encode(obj))
|
|
|
|
def test_default_namespace(self):
|
|
# Issue #77
|
|
xs = xmlschema.XMLSchema("""<?xml version="1.0" encoding="UTF-8"?>
|
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://example.com/foo">
|
|
<xs:element name="foo" type="xs:string" />
|
|
</xs:schema>""")
|
|
self.assertEqual(xs.to_dict("""<foo xmlns="http://example.com/foo">bar</foo>""",
|
|
path='/foo', namespaces={'': 'http://example.com/foo'}), 'bar')
|
|
self.assertEqual(xs.to_dict("""<foo>bar</foo>""",
|
|
path='/foo', namespaces={'': 'http://example.com/foo'}), None)
|
|
|
|
def test_complex_with_simple_content_restriction(self):
|
|
xs = self.schema_class(self.casepath('features/derivations/complex-with-simple-content-restriction.xsd'))
|
|
self.assertTrue(xs.is_valid('<value>10</value>'))
|
|
self.assertFalse(xs.is_valid('<value>alpha</value>'))
|
|
self.assertEqual(xs.decode('<value>10</value>'), 10)
|
|
|
|
def test_union_types(self):
|
|
# For testing issue #103
|
|
decimal_or_nan = self.st_schema.types['myType']
|
|
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 = """<?xml version="1.0" encoding="utf-8"?>
|
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
|
<xs:element name="root" type="root" default="default_value"/>
|
|
<xs:complexType name="root">
|
|
<xs:simpleContent>
|
|
<xs:extension base="xs:string">
|
|
<xs:attribute name="attr" type="xs:string"/>
|
|
<xs:attribute name="attrWithDefault" type="xs:string" default="default_value"/>
|
|
<xs:attribute name="attrWithFixed" type="xs:string" fixed="fixed_value"/>
|
|
</xs:extension>
|
|
</xs:simpleContent>
|
|
</xs:complexType>
|
|
<xs:element name="simple_root" type="xs:string" default="default_value"/>
|
|
</xs:schema>"""
|
|
|
|
schema = self.schema_class(xsd_text)
|
|
self.assertEqual(schema.to_dict("<root>text</root>"),
|
|
{'@attrWithDefault': 'default_value',
|
|
'@attrWithFixed': 'fixed_value',
|
|
'$': 'text'})
|
|
self.assertEqual(schema.to_dict("<root/>"),
|
|
{'@attrWithDefault': 'default_value',
|
|
'@attrWithFixed': 'fixed_value',
|
|
'$': 'default_value'})
|
|
self.assertEqual(schema.to_dict("""<root attr="attr_value">text</root>"""),
|
|
{'$': 'text',
|
|
'@attr': 'attr_value',
|
|
'@attrWithDefault': 'default_value',
|
|
'@attrWithFixed': 'fixed_value'})
|
|
|
|
self.assertEqual(schema.to_dict("<root>text</root>", use_defaults=False),
|
|
{'@attrWithFixed': 'fixed_value', '$': 'text'})
|
|
self.assertEqual(schema.to_dict("""<root attr="attr_value">text</root>""", use_defaults=False),
|
|
{'$': 'text', '@attr': 'attr_value', '@attrWithFixed': 'fixed_value'})
|
|
self.assertEqual(schema.to_dict("<root/>", use_defaults=False), {'@attrWithFixed': 'fixed_value'})
|
|
|
|
self.assertEqual(schema.to_dict("<simple_root/>"), 'default_value')
|
|
self.assertIsNone(schema.to_dict("<simple_root/>", use_defaults=False))
|
|
|
|
def test_validation_errors(self):
|
|
xsd_text = """<?xml version="1.0" encoding="utf-8"?>
|
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
|
<xs:element name="root" type="rootType" />
|
|
<xs:complexType name="rootType">
|
|
<xs:simpleContent>
|
|
<xs:extension base="xs:int">
|
|
<xs:attribute name="int_attr" type="xs:int"/>
|
|
<xs:attribute name="bool_attr" type="xs:boolean"/>
|
|
</xs:extension>
|
|
</xs:simpleContent>
|
|
</xs:complexType>
|
|
<xs:element name="simple_root" type="xs:float"/>
|
|
</xs:schema>"""
|
|
|
|
schema = self.schema_class(xsd_text)
|
|
|
|
self.assertIsNone(schema.to_dict("<simple_root>alpha</simple_root>", validation='lax')[0])
|
|
self.assertEqual(schema.to_dict("<root int_attr='10'>20</root>"), {'@int_attr': 10, '$': 20})
|
|
self.assertEqual(schema.to_dict("<root int_attr='wrong'>20</root>", validation='lax')[0],
|
|
{'@int_attr': None, '$': 20})
|
|
self.assertEqual(schema.to_dict("<root int_attr='wrong'>20</root>", validation='skip'),
|
|
{'@int_attr': 'wrong', '$': 20})
|
|
|
|
def test_error_message(self):
|
|
schema = self.schema_class(self.casepath('issues/issue_115/Rotation.xsd'))
|
|
rotation_data = '<tns:rotation xmlns:tns="http://www.example.org/Rotation/" ' \
|
|
'pitch="0.0" roll="0.0" yaw="-1.0" />'
|
|
|
|
message_lines = []
|
|
try:
|
|
schema.decode(rotation_data)
|
|
except Exception as err:
|
|
message_lines = unicode_type(err).split('\n')
|
|
|
|
self.assertTrue(message_lines, msg="Empty error message!")
|
|
self.assertEqual(message_lines[-6], 'Instance:')
|
|
self.assertEqual(message_lines[-4].strip(), rotation_data)
|
|
self.assertEqual(message_lines[-2], 'Path: /tns:rotation')
|
|
|
|
|
|
class TestDecoding11(TestDecoding):
|
|
schema_class = XMLSchema11
|
|
|
|
def test_datetime_types(self):
|
|
xs = self.get_schema('<xs:element name="dt" type="xs:dateTime"/>')
|
|
self.assertEqual(xs.decode('<dt>2019-01-01T13:40:00</dt>'), '2019-01-01T13:40:00')
|
|
self.assertEqual(xs.decode('<dt>2019-01-01T13:40:00</dt>', datetime_types=True),
|
|
datatypes.DateTime.fromstring('2019-01-01T13:40:00'))
|
|
|
|
xs = self.get_schema('<xs:element name="dt" type="xs:date"/>')
|
|
self.assertEqual(xs.decode('<dt>2001-04-15</dt>'), '2001-04-15')
|
|
self.assertEqual(xs.decode('<dt>2001-04-15</dt>', datetime_types=True),
|
|
datatypes.Date.fromstring('2001-04-15'))
|
|
|
|
def test_derived_duration_types(self):
|
|
xs = self.get_schema('<xs:element name="td" type="xs:yearMonthDuration"/>')
|
|
self.assertEqual(xs.decode('<td>P0Y4M</td>'), 'P0Y4M')
|
|
self.assertEqual(xs.decode('<td>P2Y10M</td>', datetime_types=True),
|
|
datatypes.Duration.fromstring('P2Y10M'))
|
|
|
|
xs = self.get_schema('<xs:element name="td" type="xs:dayTimeDuration"/>')
|
|
self.assertEqual(xs.decode('<td>P2DT6H30M30.001S</td>'), 'P2DT6H30M30.001S')
|
|
self.assertEqual(xs.decode('<td>P2DT26H</td>'), 'P2DT26H')
|
|
self.assertEqual(xs.decode('<td>P2DT6H30M30.001S</td>', datetime_types=True),
|
|
datatypes.Duration.fromstring('P2DT6H30M30.001S'))
|
|
|
|
def test_type_alternatives(self):
|
|
xs = self.schema_class(self.casepath('features/elements/type_alternatives-no-ns.xsd'))
|
|
self.assertTrue(xs.is_valid('<value choice="int">10</value>'))
|
|
self.assertFalse(xs.is_valid('<value choice="int">10.1</value>'))
|
|
self.assertTrue(xs.is_valid('<value choice="float">10.1</value>'))
|
|
self.assertFalse(xs.is_valid('<value choice="float">alpha</value>'))
|
|
self.assertFalse(xs.is_valid('<value choice="bool">alpha</value>'))
|
|
self.assertTrue(xs.is_valid('<value choice="bool">0</value>'))
|
|
self.assertTrue(xs.is_valid('<value choice="bool">true</value>'))
|
|
|
|
xs = self.schema_class(self.casepath('features/elements/type_alternatives.xsd'))
|
|
self.assertTrue(xs.is_valid('<ns:value xmlns:ns="ns" choice="int">10</ns:value>'))
|
|
self.assertFalse(xs.is_valid('<ns:value xmlns:ns="ns" choice="int">10.1</ns:value>'))
|
|
self.assertTrue(xs.is_valid('<ns:value xmlns:ns="ns" choice="float">10.1</ns:value>'))
|
|
self.assertFalse(xs.is_valid('<ns:value xmlns:ns="ns" choice="float">alpha</ns:value>'))
|
|
self.assertFalse(xs.is_valid('<ns:value xmlns:ns="ns" choice="bool">alpha</ns:value>'))
|
|
self.assertTrue(xs.is_valid('<ns:value xmlns:ns="ns" choice="bool">0</ns:value>'))
|
|
self.assertTrue(xs.is_valid('<ns:value xmlns:ns="ns" choice="bool">true</ns:value>'))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
from xmlschema.tests import print_test_header
|
|
|
|
print_test_header()
|
|
unittest.main()
|