Skip to content

Commit

Permalink
Add process_skipped optional argument to decoding/encoding
Browse files Browse the repository at this point in the history
  - Process contents matched by 'skip' wildcard as 'lax'
  - Fix test_exception_repr() including xsd prefix
  • Loading branch information
brunato committed Dec 8, 2021
1 parent 9c823ad commit dab51b7
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 15 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ CHANGELOG
======================
* Improve error reporting for encoded data (issue #275)
* Fix attribute duplicates in attribute group (issue #276)
* Add process_skipped optional argument to decoding/encoding

`v1.9.0`_ (2021-11-30)
======================
Expand Down
28 changes: 27 additions & 1 deletion tests/validation/test_decoding.py
Original file line number Diff line number Diff line change
Expand Up @@ -778,9 +778,35 @@ def test_any_type_decoding(self):
self.assertIsNone(any_type.decode(xml_data_1))
xml_data_2 = ElementTree.fromstring('<root>\n <child_1/>\n <child_2/>\n</root>')

# Fix for processContent='lax' (issue 273, previously result was None)
# Fix for processContents='lax' (issue 273, previously result was None)
self.assertEqual(any_type.decode(xml_data_2), {'child_1': [None], 'child_2': [None]})

def test_skip_wildcard_decoding(self):
schema = self.schema_class("""<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="foo">
<xs:complexType>
<xs:sequence>
<xs:any processContents="skip"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>""")

self.assertIsNone(schema.decode('<foo><bar/></foo>'))

obj = schema.decode('<foo><bar/></foo>', process_skipped=True)
self.assertEqual(obj, {'bar': None})

root = schema.encode(obj)
self.assertEqual(root.tag, 'foo')
self.assertEqual(len(root), 0)

root = schema.encode(obj, process_skipped=True)
self.assertEqual(root.tag, 'foo')
self.assertEqual(len(root), 1)
self.assertEqual(root[0].tag, 'bar')

def test_any_type_decoding__issue_273(self):
schema = self.schema_class(self.casepath('issues/issue_273/issue_273.xsd'))
data = schema.to_dict(self.casepath('issues/issue_273/issue_273.xml'))
Expand Down
2 changes: 1 addition & 1 deletion tests/validators/test_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def test_exception_repr(self):
self.assertEqual(lines[0], 'unknown error:', msg=output)
self.assertEqual(lines[2], 'Schema:', msg=output)
self.assertRegex(lines[4].strip(), '^<(xs:)?schema ', msg=output)
self.assertRegex(lines[-2].strip(), '</(xs:)?schema>$', msg=output)
self.assertRegex(lines[-2].strip(), '</(xs:|xsd:)?schema>$', msg=output)

@unittest.skipIf(lxml_etree is None, 'lxml is not installed ...')
def test_exception_repr_lxml(self):
Expand Down
8 changes: 7 additions & 1 deletion xmlschema/validators/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -1856,6 +1856,7 @@ def iter_decode(self, source: Union[XMLSourceType, XMLResource],
filler: Optional[Callable[[Union[XsdElement, XsdAttribute]], Any]] = None,
fill_missing: bool = False,
keep_unknown: bool = False,
process_skipped: bool = False,
max_depth: Optional[int] = None,
depth_filler: Optional[Callable[[XsdElement], Any]] = None,
value_hook: Optional[Callable[[AtomicValueType, BaseXsdType], Any]] = None,
Expand Down Expand Up @@ -1895,6 +1896,8 @@ def iter_decode(self, source: Union[XMLSourceType, XMLResource],
The filling value is `None` or a typed value if the *filler* callback is provided.
:param keep_unknown: if set to `True` unknown tags are kept and are decoded with \
*xs:anyType*. For default unknown tags not decoded by a wildcard are discarded.
:param process_skipped: process XML data that match a wildcard with \
`processContents='skip'`.
:param max_depth: maximum level of decoding, for default there is no limit. \
With lazy resources is set to `source.lazy_depth` for managing lazy decoding.
:param depth_filler: an optional callback function to replace data over the \
Expand Down Expand Up @@ -1946,6 +1949,8 @@ def iter_decode(self, source: Union[XMLSourceType, XMLResource],
kwargs['fill_missing'] = fill_missing
if keep_unknown:
kwargs['keep_unknown'] = keep_unknown
if process_skipped:
kwargs['process_skipped'] = process_skipped
if max_depth is not None:
kwargs['max_depth'] = max_depth
if depth_filler is not None:
Expand Down Expand Up @@ -2046,7 +2051,8 @@ def iter_encode(self, obj: Any, path: Optional[str] = None, validation: str = 'l
:param unordered: a flag for explicitly activating unordered encoding mode for \
content model data. This mode uses content models for a reordered-by-model \
iteration of the child elements.
:param kwargs: keyword arguments containing options for converter.
:param kwargs: keyword arguments with other options for encoding and for \
building the converter instance.
:return: yields an Element instance/s or validation/encoding errors.
"""
self.check_validator(validation)
Expand Down
31 changes: 19 additions & 12 deletions xmlschema/validators/wildcards.py
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,8 @@ def iter_decode(self, obj: ElementType, validation: str = 'lax', **kwargs: Any)
yield self.validation_error(validation, reason, obj, **kwargs)

if self.process_contents == 'skip':
return
if 'process_skipped' not in kwargs or not kwargs['process_skipped']:
return

namespace = get_namespace(obj.tag)
if not self.maps.load_namespace(namespace):
Expand All @@ -482,14 +483,15 @@ def iter_decode(self, obj: ElementType, validation: str = 'lax', **kwargs: Any)
return

if XSI_TYPE in obj.attrib:
if self.process_contents == 'lax':
if self.process_contents == 'strict':
xsd_element = self.maps.validator.create_element(
obj.tag, parent=self, nillable='true', form='unqualified'
obj.tag, parent=self, form='unqualified'
)
else:
xsd_element = self.maps.validator.create_element(
obj.tag, parent=self, form='unqualified'
obj.tag, parent=self, nillable='true', form='unqualified'
)

yield from xsd_element.iter_decode(obj, validation, **kwargs)
return

Expand All @@ -507,7 +509,8 @@ def iter_encode(self, obj: Tuple[str, ElementType], validation: str = 'lax', **k
yield self.validation_error(validation, reason, value, **kwargs)

if self.process_contents == 'skip':
return
if 'process_skipped' not in kwargs or not kwargs['process_skipped']:
return

if not self.maps.load_namespace(namespace):
reason = f"unavailable namespace {namespace!r}"
Expand All @@ -522,13 +525,13 @@ def iter_encode(self, obj: Tuple[str, ElementType], validation: str = 'lax', **k

# Check if there is an xsi:type attribute, but it has to extract
# attributes using the converter instance.
if self.process_contents == 'lax':
if self.process_contents == 'strict':
xsd_element = self.maps.validator.create_element(
name, parent=self, nillable='true', form='unqualified'
name, parent=self, form='unqualified'
)
else:
xsd_element = self.maps.validator.create_element(
name, parent=self, form='unqualified'
name, parent=self, nillable='true', form='unqualified'
)

try:
Expand Down Expand Up @@ -642,8 +645,10 @@ def iter_decode(self, obj: Tuple[str, str], validation: str = 'lax', **kwargs: A
yield self.validation_error(validation, reason, obj, **kwargs)

if self.process_contents == 'skip':
return
elif self.maps.load_namespace(get_namespace(name)):
if 'process_skipped' not in kwargs or not kwargs['process_skipped']:
return

if self.maps.load_namespace(get_namespace(name)):
try:
xsd_attribute = self.maps.lookup_attribute(name)
except LookupError:
Expand All @@ -670,8 +675,10 @@ def iter_encode(self, obj: Tuple[str, AtomicValueType], validation: str = 'lax',
yield self.validation_error(validation, reason, obj, **kwargs)

if self.process_contents == 'skip':
return
elif self.maps.load_namespace(namespace):
if 'process_skipped' not in kwargs or not kwargs['process_skipped']:
return

if self.maps.load_namespace(namespace):
try:
xsd_attribute = self.maps.lookup_attribute(name)
except LookupError:
Expand Down

0 comments on commit dab51b7

Please sign in to comment.