Skip to content

Commit

Permalink
Merge branch 'develop' for upgrade to v1.0.17
Browse files Browse the repository at this point in the history
  • Loading branch information
brunato committed Dec 21, 2019
2 parents a64ef0b + 2281aef commit 6c8085a
Show file tree
Hide file tree
Showing 34 changed files with 842 additions and 532 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
CHANGELOG
*********

`v1.0.17`_ (2019-12-22)
=======================
* Enhancement of validation-only speed (~15%)
* Added *is_valid()* and *iter_errors()* to module API

`v1.0.16`_ (2019-11-18)
=======================
* Improved XMLResource class for working with compressed files
Expand Down Expand Up @@ -272,3 +277,4 @@ v0.9.6 (2017-05-05)
.. _v1.0.14: https://github.com/brunato/xmlschema/compare/v1.0.13...v1.0.14
.. _v1.0.15: https://github.com/brunato/xmlschema/compare/v1.0.14...v1.0.15
.. _v1.0.16: https://github.com/brunato/xmlschema/compare/v1.0.15...v1.0.16
.. _v1.0.17: https://github.com/brunato/xmlschema/compare/v1.0.16...v1.0.17
3 changes: 3 additions & 0 deletions doc/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Document level API
------------------

.. autofunction:: xmlschema.validate
.. autofunction:: xmlschema.is_valid
.. autofunction:: xmlschema.iter_errors
.. autofunction:: xmlschema.to_dict
.. autofunction:: xmlschema.to_json
.. autofunction:: xmlschema.from_json
Expand Down Expand Up @@ -47,6 +49,7 @@ Schema level API
.. automethod:: create_schema
.. automethod:: create_any_content_group
.. automethod:: create_any_attribute_group
.. automethod:: create_any_type

.. automethod:: get_locations
.. automethod:: include_schema
Expand Down
2 changes: 1 addition & 1 deletion doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
# The short X.Y version.
version = '1.0'
# The full version, including alpha/beta/rc tags.
release = '1.0.16'
release = '1.0.17'

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
Expand Down
4 changes: 2 additions & 2 deletions publiccode.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ publiccodeYmlVersion: '0.2'
name: xmlschema
url: 'https://github.com/sissaschool/xmlschema'
landingURL: 'https://github.com/sissaschool/xmlschema'
releaseDate: '2019-11-18'
softwareVersion: v1.0.16
releaseDate: '2019-12-22'
softwareVersion: v1.0.17
developmentStatus: stable
platforms:
- linux
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def run(self):

setup(
name='xmlschema',
version='1.0.16',
version='1.0.17',
setup_requires=['elementpath~=1.3.0'],
install_requires=['elementpath~=1.3.0'],
packages=['xmlschema'],
Expand Down
4 changes: 2 additions & 2 deletions xmlschema/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
ElementData, XMLSchemaConverter, UnorderedConverter, ParkerConverter,
BadgerFishConverter, AbderaConverter, JsonMLConverter
)
from .documents import validate, to_dict, to_json, from_json
from .documents import validate, is_valid, iter_errors, to_dict, to_json, from_json

from .validators import (
XMLSchemaValidatorError, XMLSchemaParseError, XMLSchemaNotBuiltError,
Expand All @@ -31,7 +31,7 @@
XsdGlobals, XMLSchemaBase, XMLSchema, XMLSchema10, XMLSchema11
)

__version__ = '1.0.16'
__version__ = '1.0.17'
__author__ = "Davide Brunato"
__contact__ = "[email protected]"
__copyright__ = "Copyright 2016-2019, SISSA"
Expand Down
13 changes: 9 additions & 4 deletions xmlschema/converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,8 @@ def element_decode(self, data, xsd_element, level=0):
if level == 0 and xsd_element.is_global() and not self.strip_namespaces and self:
schema_namespaces = set(xsd_element.namespaces.values())
result_dict.update(
('%s:%s' % (self.ns_prefix, k) if k else self.ns_prefix, v) for k, v in self.items()
('%s:%s' % (self.ns_prefix, k) if k else self.ns_prefix, v)
for k, v in self._namespaces.items()
if v in schema_namespaces or v == XSI_NAMESPACE
)

Expand Down Expand Up @@ -325,6 +326,8 @@ def element_encode(self, obj, xsd_element, level=0):
if not isinstance(obj, (self.dict, dict)):
if xsd_element.type.is_simple() or xsd_element.type.has_simple_content():
return ElementData(tag, obj, None, {})
elif xsd_element.type.mixed and not isinstance(obj, list):
return ElementData(tag, obj, None, {})
else:
return ElementData(tag, None, obj, {})

Expand Down Expand Up @@ -900,7 +903,9 @@ def element_decode(self, data, xsd_element, level=0):
])

if level == 0 and xsd_element.is_global() and not self.strip_namespaces and self:
attributes.update([('xmlns:%s' % k if k else 'xmlns', v) for k, v in self.items()])
attributes.update(
[('xmlns:%s' % k if k else 'xmlns', v) for k, v in self._namespaces.items()]
)
if attributes:
result_list.insert(1, attributes)
return result_list
Expand All @@ -913,7 +918,7 @@ def element_encode(self, obj, xsd_element, level=0):

data_len = len(obj)
if data_len == 1:
if not xsd_element.is_matching(unmap_qname(obj[0]), self.get('')):
if not xsd_element.is_matching(unmap_qname(obj[0]), self._namespaces.get('')):
raise XMLSchemaValueError("Unmatched tag")
return ElementData(xsd_element.name, None, None, attributes)

Expand All @@ -930,7 +935,7 @@ def element_encode(self, obj, xsd_element, level=0):
else:
content_index = 2

if not xsd_element.is_matching(unmap_qname(obj[0]), self.get('')):
if not xsd_element.is_matching(unmap_qname(obj[0]), self._namespaces.get('')):
raise XMLSchemaValueError("Unmatched tag")

if data_len <= content_index:
Expand Down
32 changes: 26 additions & 6 deletions xmlschema/documents.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,20 @@

from .compat import ordered_dict_class
from .resources import fetch_schema_locations, XMLResource
from .validators.schema import XMLSchema, XMLSchemaBase
from .validators import XMLSchema, XMLSchemaBase


def get_context(source, schema=None, cls=None, locations=None, base_url=None,
defuse='remote', timeout=300, lazy=False):
"""
Helper method for obtaining XML document validation/decode context.
Return an XMLResource instance and a schema instance.
Get the XML document validation/decode context.
:return: an XMLResource instance and a schema instance.
"""
if cls is None:
cls = XMLSchema
if not isinstance(source, XMLResource):
source = XMLResource(source, base_url, defuse=defuse, timeout=timeout, lazy=lazy)

try:
schema, locations = fetch_schema_locations(source, locations, base_url=base_url)
Expand All @@ -36,9 +39,6 @@ def get_context(source, schema=None, cls=None, locations=None, base_url=None,
else:
schema = cls(schema, validation='strict', locations=locations, defuse=defuse, timeout=timeout)

if not isinstance(source, XMLResource):
source = XMLResource(source, defuse=defuse, timeout=timeout, lazy=lazy)

return source, schema


Expand Down Expand Up @@ -75,6 +75,26 @@ def validate(xml_document, schema=None, cls=None, path=None, schema_path=None, u
schema.validate(source, path, schema_path, use_defaults, namespaces)


def is_valid(xml_document, schema=None, cls=None, path=None, schema_path=None, use_defaults=True,
namespaces=None, locations=None, base_url=None, defuse='remote', timeout=300, lazy=False):
"""
Like :meth:`validate` except that do not raises an exception but returns ``True`` if
the XML document is valid, ``False`` if it's invalid.
"""
source, schema = get_context(xml_document, schema, cls, locations, base_url, defuse, timeout, lazy)
return schema.is_valid(source, path, schema_path, use_defaults, namespaces)


def iter_errors(xml_document, schema=None, cls=None, path=None, schema_path=None, use_defaults=True,
namespaces=None, locations=None, base_url=None, defuse='remote', timeout=300, lazy=False):
"""
Creates an iterator for the errors generated by the validation of an XML document.
Takes the same arguments of the function :meth:`validate`.
"""
source, schema = get_context(xml_document, schema, cls, locations, base_url, defuse, timeout, lazy)
return schema.iter_errors(source, path, schema_path, use_defaults, namespaces)


def to_dict(xml_document, schema=None, cls=None, path=None, process_namespaces=True,
locations=None, base_url=None, defuse='remote', timeout=300, lazy=False, **kwargs):
"""
Expand Down
14 changes: 13 additions & 1 deletion xmlschema/etree.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from .compat import PY3
from .exceptions import XMLSchemaTypeError
from .namespaces import XSLT_NAMESPACE, HFP_NAMESPACE, VC_NAMESPACE, get_namespace
from .qnames import get_qname, qname_to_prefixed
from .qnames import get_qname, qname_to_prefixed, XSI_SCHEMA_LOCATION, XSI_NONS_SCHEMA_LOCATION

###
# Programmatic import of xml.etree.ElementTree
Expand Down Expand Up @@ -263,6 +263,18 @@ def etree_getpath(elem, root, namespaces=None, relative=True, add_position=False
return path


def etree_iter_location_hints(elem):
"""Yields schema location hints contained in the attributes of an element."""
if XSI_SCHEMA_LOCATION in elem.attrib:
locations = elem.attrib[XSI_SCHEMA_LOCATION].split()
for ns, url in zip(locations[0::2], locations[1::2]):
yield ns, url

if XSI_NONS_SCHEMA_LOCATION in elem.attrib:
for url in elem.attrib[XSI_NONS_SCHEMA_LOCATION].split():
yield '', url


def etree_elements_assert_equal(elem, other, strict=True, skip_comments=True):
"""
Tests the equality of two XML Element trees.
Expand Down
54 changes: 43 additions & 11 deletions xmlschema/namespaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,42 +119,74 @@ def clear(self):

class NamespaceMapper(MutableMapping):
"""
A class to map/unmap namespace prefixes to URIs. The
A class to map/unmap namespace prefixes to URIs. The mapped namespaces are
automatically registered when set. Namespaces can be updated overwriting
the existing registration or inserted using an alternative prefix.
:param namespaces: Initial data with namespace prefixes and URIs.
:param namespaces: initial data with namespace prefixes and URIs.
:param register_namespace: a two-arguments function for registering namespaces \
on ElementTree module.
"""
def __init__(self, namespaces=None, register_namespace=None):
self._namespaces = {}
self.register_namespace = register_namespace
if namespaces is not None:
self.update(namespaces)
self._namespaces.update(namespaces)

def __getitem__(self, key):
return self._namespaces[key]
def __getitem__(self, prefix):
return self._namespaces[prefix]

def __setitem__(self, key, value):
self._namespaces[key] = value
def __setitem__(self, prefix, uri):
self._namespaces[prefix] = uri
try:
self.register_namespace(key, value)
self.register_namespace(prefix, uri)
except (TypeError, ValueError):
pass

def __delitem__(self, key):
del self._namespaces[key]
def __delitem__(self, prefix):
del self._namespaces[prefix]

def __iter__(self):
return iter(self._namespaces)

def __len__(self):
return len(self._namespaces)

@property
def namespaces(self):
return self._namespaces

@property
def default_namespace(self):
return self._namespaces.get('')

def clear(self):
self._namespaces.clear()

def insert_item(self, prefix, uri):
"""
A method for setting an item that checks the prefix before inserting.
In case of collision the prefix is changed adding a numerical suffix.
"""
if not prefix:
if '' not in self._namespaces:
self._namespaces[prefix] = uri
return
elif self._namespaces[''] == uri:
return
prefix = 'default'

while prefix in self._namespaces:
if self._namespaces[prefix] == uri:
return
match = re.search(r'(\d+)$', prefix)
if match:
index = int(match.group()) + 1
prefix = prefix[:match.span()[0]] + str(index)
else:
prefix += '0'
self._namespaces[prefix] = uri

def map_qname(self, qname):
"""
Converts an extended QName to the prefixed format. Only registered
Expand All @@ -170,7 +202,7 @@ def map_qname(self, qname):
return qname

qname_uri = get_namespace(qname)
for prefix, uri in self.items():
for prefix, uri in self._namespaces.items():
if uri != qname_uri:
continue
if prefix:
Expand Down
Loading

0 comments on commit 6c8085a

Please sign in to comment.