Update schema class

- now parses also WSDL-SOAP original schema

  xmlschema/schema.py module:
    - more XSD compiliant imports and includes
    - added 'locations' argument to schema class init
    - improved schema location hints
    - revised documentation with more autodoc
    - schema class code cleaning
    - schema class creation is now based on a metaclass

  xmlschema/resources.py module:
    - added iter_schema_location_hints
    - fetch_schema code cleaned
    - removed old functions for retrieving XSI attributes

  xmlschema/exceptions.py module:
    - added index and expected attributes to XMLSchemaChildrenValidationError

  Updated docs using more autodocs

  Added requirements-dev.txt
This commit is contained in:
Davide Brunato 2018-01-11 10:59:43 +01:00
parent d56208106b
commit c31fc194d8
26 changed files with 899 additions and 1254 deletions

View File

@ -6,6 +6,8 @@ v0.9.18
=======
* Fixed issue #34 (min_occurs == 0 check in XsdGroup.is_emptiable)
* Updated copyright information
* Updated schema class creation (now use a metaclass)
* Added index and expected attributes to XMLSchemaChildrenValidationError
v0.9.17
=======

View File

@ -1,225 +1,20 @@
# Makefile for Sphinx documentation
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
SPHINXPROJ = xmlschema
SOURCEDIR = .
BUILDDIR = _build
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help
# Put it first so that "make" without argument is like "make help".
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " applehelp to make an Apple Help Book"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " epub3 to make an epub3"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " xml to make Docutils-native XML files"
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
@echo " coverage to run coverage check of the documentation (if enabled)"
@echo " dummy to check syntax errors of document sources"
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: clean
clean:
rm -rf $(BUILDDIR)/*
.PHONY: help Makefile
.PHONY: html
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
.PHONY: dirhtml
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
.PHONY: singlehtml
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
.PHONY: pickle
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
.PHONY: json
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
.PHONY: htmlhelp
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
.PHONY: qthelp
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/xmlschema.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/xmlschema.qhc"
.PHONY: applehelp
applehelp:
$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
@echo
@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
@echo "N.B. You won't be able to view it unless you put it in" \
"~/Library/Documentation/Help or install it in your application" \
"bundle."
.PHONY: devhelp
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/xmlschema"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/xmlschema"
@echo "# devhelp"
.PHONY: epub
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
.PHONY: epub3
epub3:
$(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3
@echo
@echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3."
.PHONY: latex
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
.PHONY: latexpdf
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
.PHONY: latexpdfja
latexpdfja:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through platex and dvipdfmx..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
.PHONY: text
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
.PHONY: man
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
.PHONY: texinfo
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
.PHONY: info
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
.PHONY: gettext
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
.PHONY: changes
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
.PHONY: linkcheck
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
.PHONY: doctest
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
.PHONY: coverage
coverage:
$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
@echo "Testing of coverage in the sources finished, look at the " \
"results in $(BUILDDIR)/coverage/python.txt."
.PHONY: xml
xml:
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
@echo
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
.PHONY: pseudoxml
pseudoxml:
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
@echo
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
.PHONY: dummy
dummy:
$(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy
@echo
@echo "Build finished. Dummy builder generates no files."
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

View File

@ -43,210 +43,53 @@ Module level API
Schema level API
----------------
.. class:: XMLSchema(source, namespace=None, validation='strict', global_maps=None, \
converter=None, build=True)
.. class:: xmlschema.XMLSchema_v1_0
The class for an XML Schema instance.
*source* can be an URI that reference to a resource or a file path or a file-like
object or a string containing the schema. *namespace* is an optional argument
that contains the URI of the namespace. When specified it must be equal to the
*targetNamespace* declared in the schema. *validation* defines the XSD validation
mode to use for build the schema, can be 'strict', 'lax' or 'skip'.
*global_maps* is an optional argument containing an :class:`XsdGlobals`
instance, a mediator object for sharing declaration data between dependents
schema instances. *converter* is an optional argument that can be an
:class:`XMLSchemaConverter` subclass or instance, used for defining the default
XML data converter for XML Schema instance. *build* is a boolean value that defines
whether build the schema maps.
The class for XSD v1.0 schema instances. It's generated by the meta-class :class:`XMLSchemaMeta`
and takes the same API of :class:`XMLSchemaBase`.
.. attribute::root
.. autoclass:: xmlschema.XMLSchema
The XML schema Element root.
.. autoclass:: xmlschema.XMLSchemaBase
.. attribute:: text
.. autoattribute:: id
.. autoattribute:: version
.. autoattribute:: attribute_form_default
.. autoattribute:: element_form_default
.. autoattribute:: block_default
.. autoattribute:: schema_location
.. autoattribute:: no_namespace_schema_location
.. autoattribute:: target_prefix
.. autoattribute:: base_url
.. autoattribute:: parent_map
The XML schema source text.
.. automethod:: get_locations
.. automethod:: include_schema
.. automethod:: import_schema
.. automethod:: create_schema
.. attribute:: url
.. automethod:: check_schema
.. automethod:: build
.. autoattribute:: built
.. autoattribute:: validation_attempted
.. autoattribute:: validity
.. autoattribute:: all_errors
.. automethod:: iter_components
.. automethod:: iter_globals
The schema resource URL. It's `None` if the schema is built from a string.
.. automethod:: validate
.. automethod:: is_valid
.. automethod:: decode
.. automethod:: get_converter
.. attribute:: target_namespace
.. autoattribute:: to_dict
:annotate: = An alias for :classmethod:`decode`.
Is the *targetNamespace* of the schema, the namespace to which the schema's
declarations belong. If it's empty none namespace is associated with the schema.
In this case the schema declarations can be reused from other namespaces as
*chameleon* definitions.
.. py:attribute:: element_form
*elementFormDefault* for the schema, default to ``'unqualified'``.
.. py:attribute:: attribute_form
*attributeFormDefault* for the schema, default to ``'unqualified'``.
.. attribute:: schema_locations
.. py:attribute:: no_namespace_schema_location
Schema location hints. Usually specified in XML files, sometimes it's used
in schemas when the namespace definitions are splitted into more files.
.. attribute:: built
Property that is ``True`` if schema declarations has been parsed and
built, ``False`` if not.
.. attribute:: namespaces
A map from prefixes used by the schema to the correspondent namespace URI.
This mapping can be different between each schema resource, so it's saved
at schema's instance level.
.. attribute:: converter
The default `XMLSchemaConverter` instance used for XML data conversion.
.. attribute:: maps
An instance of :class:`XsdGlobal` that match the *global_maps* argument or
a new :class:`XsdGlobal` object when this argument is not provided.
.. attribute:: types
.. attribute:: attributes
.. attribute:: attribute_groups
.. attribute:: groups
.. attribute:: elements
.. attribute:: base_elements
Dictionary views of the global declarations defined by the schema instance.
The global declarations are taken from the declarations of :attr:`maps`.
In the schema's views the global declaration names are registered with the
local part only.
.. classmethod:: create_schema(*args, **kwargs)
Creates a new schema instance of the same class of the caller.
.. classmethod:: check_schema(schema)
Validates the given schema against the XSD :attr:`META_SCHEMA`.
:raises: :exc:`XMLSchemaValidationError` if the schema is invalid.
.. method:: import_schema(namespace, location, base_url=None, force=False)
Imports a schema for an external namespace, from a specific URL.
*namespace* is the URI of the external namespace.
*location* is the URL of the schema.
*base_url* is an optional base URL for fetching the schema resource.
If *force* is set to `True` imports the schema also if the namespace
is already imported.
.. method:: include_schema(location, base_url=None)
Include a schema for the namespace, from a specific URL.
*location* is the URL of the schema.
*base_url* is an optional base URL for fetching the schema resource.
.. method:: build()
Builds the schema maps.
.. method:: validate(xml_document, use_defaults=True)
Validates an XML document against the schema instance. *xml_document* can be
a path to a file or an URI of a resource or an opened file-like object or
an Element Tree instance or a string containing XML data. *use_defaults*
indicates whether to use default values for filling missing attributes or
elements.
:raises: :exc:`XMLSchemaValidationError` if the XML document is invalid.
.. method:: is_valid(xml_document, use_defaults=True)
Like :meth:`validate` except that do not raises an exception but returns
``True`` if the XML document is valid, ``False`` if is invalid.
.. method:: iter_errors(xml_document, path=None, use_defaults=True)
Creates an iterator for errors generated by the validation of an XML
document against the schema instance. *path* is an optional XPath expression
that defines the parts of the document that have to be validated. The XPath
expression considers the schema as the root element with global elements as its children.
*use_defaults* indicates whether to use default values for filling missing
attributes or elements.
.. method:: iter_decode(self, data, path=None, validation='lax', process_namespaces=True,\
namespaces=None, use_defaults=True, decimal_type=None, converter=None,\
dict_class=None, list_class=None)
Creates an iterator for decoding an XML document using the schema instance.
Yields objects that can be dictionaries or simple data values.
*path* is an optional XPath expression that matches the parts of the document
that have to be decoded. The XPath expression considers the schema as the root
element with global elements as its children.
*validation* defines the XSD validation mode to use for decode, can be 'strict',
'lax' or 'skip'.
*process_namespaces* indicates whether to process namespaces, using the map
provided with the argument *namespaces* and the map extracted from the XML
document.
*namespaces* is an optional mapping from namespace prefix to URI.
*use_defaults* indicates whether to use default values for filling missing
attributes or elements.
*decimal_type* conversion type for `Decimal` objects (generated by XSD `decimal`
built-in and derived types), useful if you want to generate a JSON-compatible
data structure.
*converter* an `XMLSchemaConverter` subclass or instance to use for the decoding.
*dict_class* the dictionary-like class that have to be used instead of
the default dictionary class of the `XMLSchemaConverter` subclass/instance.
*list_class* the list-like class that have to be used instead of the default
list class of the `XMLSchemaConverter` class/instance.
.. method:: decode(*args, **kwargs)
.. method:: to_dict(*args, **kwargs)
Decodes an XML document to a Python data structure using the schema instance.
You can provide the arguments and keyword arguments of :meth:`iter_decode`.
Raises an `XMLSchemaValidationError` at first decoding or validation error (when
validation is requested, that is the default).
.. method:: iter(name=None)
Creates a schema iterator for elements declarations. The iteration starts
from global XSD elements, going deep into the XSD declarations graph.
If *name* argument is not ``None`` yields only the XSD elements matching
the name.
.. method:: iterchildren(name=None)
Creates an iterator for global elements, sorted by name. If *name* is not
``None`` yields only the XSD global element matching the name.
.. method:: find(path, namespaces=None)
Finds the first XSD element or attribute declaration matching the path.
Returns an XSD declaration or ``None`` if there is no match.
*path* is an XPath expression that considers the schema as the root element
with global elements as its children.
*namespaces* is an optional mapping from namespace prefix to full name.
.. method:: findall(path, namespaces=None)
Finds all matching XSD element or attribute declarations matching the path.
Returns a list containing all matching declarations in the schema order.
An empty list is returned if there is no match.
*path* is an XPath expression that considers the schema as the root element
with global elements as its children.
*namespaces* is an optional mapping from namespace prefix to full name.
.. method:: iterfind(path, namespaces=None)
Finds all matching XSD element or attribute declarations matching the path.
Returns an iterable yielding all matching declarations in the schema order.
*path* is an XPath expression that considers the schema as the root element
with global elements as its children.
*namespaces* is an optional mapping from namespace prefix to full name.
.. automethod:: iter
.. automethod:: iterchildren
.. automethod:: find
.. automethod:: findall
.. automethod:: iterfind
XSD globals maps API

View File

@ -1,7 +1,8 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# xmlschema documentation build configuration file, created by
# sphinx-quickstart on Wed Oct 19 09:03:03 2016.
# sphinx-quickstart on Fri Jan 12 09:05:46 2018.
#
# This file is execfile()d with the current directory set to its
# containing dir.
@ -46,26 +47,22 @@ templates_path = ['_templates']
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
# The encoding of source files.
#
# source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'xmlschema'
copyright = u'2016-2018, SISSA - Scuola Internazionale Superiore di Studi Avanzati'
author = u'Davide Brunato'
project = 'xmlschema'
copyright = '2016-2018, SISSA - Scuola Internazionale Superiore di Studi Avanzati'
author = 'Davide Brunato'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = u'0.9.18'
version = '0.9.18'
# The full version, including alpha/beta/rc tags.
release = u'0.9.18'
release = '0.9.18'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
@ -74,48 +71,14 @@ release = u'0.9.18'
# Usually you set "language" from the command line for these cases.
language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#
# today = ''
#
# Else, today_fmt is used as the format for a strftime call.
#
# today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# The reST default role (used for this markup: `text`) to use for all
# documents.
#
# default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#
# add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#
# add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#
# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
# modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built documents.
# keep_warnings = False
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False
@ -125,7 +88,8 @@ todo_include_todos = False
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'alabaster'
# html_theme = 'alabaster'
html_theme = 'sphinx_rtd_theme'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
@ -133,188 +97,68 @@ html_theme = 'alabaster'
#
# html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
# html_theme_path = []
# The name for this set of Sphinx documents.
# "<project> v<release> documentation" by default.
#
# html_title = u'xmlschema v0.8a5'
# A shorter title for the navigation bar. Default is the same as html_title.
#
# html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#
# html_logo = None
# The name of an image file (relative to this directory) to use as a favicon of
# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#
# html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
# Custom sidebar templates, must be a dictionary that maps document names
# to template names.
#
# html_extra_path = []
# This is required for the alabaster theme
# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars
html_sidebars = {
'**': [
'relations.html', # needs 'show_related': True theme option to display
'searchbox.html',
]
}
# If not None, a 'Last updated on:' timestamp is inserted at every page
# bottom, using the given strftime format.
# The empty string is equivalent to '%b %d, %Y'.
#
# html_last_updated_fmt = None
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#
# html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#
# html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#
# html_additional_pages = {}
# If false, no module index is generated.
#
# html_domain_indices = True
# If false, no index is generated.
#
# html_use_index = True
# If true, the index is split into individual pages for each letter.
#
# html_split_index = False
# If true, links to the reST sources are added to the pages.
#
# html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#
# html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#
# html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#
# html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
# html_file_suffix = None
# Language to be used for generating the HTML full-text search index.
# Sphinx supports the following languages:
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh'
#
# html_search_language = 'en'
# A dictionary with options for the search language support, empty by default.
# 'ja' uses this config value.
# 'zh' user can custom change `jieba` dictionary path.
#
# html_search_options = {'type': 'default'}
# The name of a javascript file (relative to the configuration directory) that
# implements a search results scorer. If empty, the default will be used.
#
# html_search_scorer = 'scorer.js'
# -- Options for HTMLHelp output ------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = 'xmlschemadoc'
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'xmlschema.tex', u'xmlschema Documentation',
u'Davide Brunato', 'manual'),
(master_doc, 'xmlschema.tex', 'xmlschema Documentation',
'Davide Brunato', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#
# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#
# latex_use_parts = False
# If true, show page references after internal links.
#
# latex_show_pagerefs = False
# If true, show URL addresses after external links.
#
# latex_show_urls = False
# Documents to append as an appendix to all manuals.
#
# latex_appendices = []
# It false, will not define \strong, \code, itleref, \crossref ... but only
# \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added
# packages.
#
# latex_keep_old_macro_names = True
# If false, no module index is generated.
#
# latex_domain_indices = True
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'xmlschema', u'xmlschema Documentation',
(master_doc, 'xmlschema', 'xmlschema Documentation',
[author], 1)
]
# If true, show URL addresses after external links.
#
# man_show_urls = False
# -- Options for Texinfo output -------------------------------------------
@ -322,26 +166,11 @@ man_pages = [
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'xmlschema', u'xmlschema Documentation',
(master_doc, 'xmlschema', 'xmlschema Documentation',
author, 'xmlschema', 'One line description of project.',
'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
#
# texinfo_appendices = []
# If false, no module index is generated.
#
# texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#
# texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node's menu.
#
# texinfo_no_detailmenu = False
# -- Options for Epub output ----------------------------------------------
@ -352,24 +181,6 @@ epub_author = author
epub_publisher = author
epub_copyright = copyright
# The basename for the epub file. It defaults to the project name.
# epub_basename = project
# The HTML theme for the epub output. Since the default themes are not
# optimized for small screen space, using the same theme for HTML and epub
# output is usually not wise. This defaults to 'epub', a theme designed to save
# visual space.
#
# epub_theme = 'epub'
# The language of the text. It defaults to the language option
# or 'en' if the language is not set.
#
# epub_language = ''
# The scheme of the identifier. Typical schemes are ISBN or URL.
# epub_scheme = ''
# The unique identifier of the text. This can be a ISBN number
# or the project homepage.
#
@ -379,51 +190,7 @@ epub_copyright = copyright
#
# epub_uid = ''
# A tuple containing the cover image and cover page html template filenames.
#
# epub_cover = ()
# A sequence of (type, uri, title) tuples for the guide element of content.opf.
#
# epub_guide = ()
# HTML files that should be inserted before the pages created by sphinx.
# The format is a list of tuples containing the path and title.
#
# epub_pre_files = []
# HTML files that should be inserted after the pages created by sphinx.
# The format is a list of tuples containing the path and title.
#
# epub_post_files = []
# A list of files that should not be packed into the epub file.
epub_exclude_files = ['search.html']
# The depth of the table of contents in toc.ncx.
#
# epub_tocdepth = 3
# Allow duplicate toc entries.
#
# epub_tocdup = True
# Choose between 'default' and 'includehidden'.
#
# epub_tocscope = 'default'
# Fix unsupported image types using the Pillow.
#
# epub_fix_images = False
# Scale large images.
#
# epub_max_image_width = 0
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#
# epub_show_urls = 'inline'
# If false, no index is generated.
#
# epub_use_index = True

View File

@ -1,10 +1,10 @@
.. xmlschema documentation master file, created by
sphinx-quickstart on Wed Oct 19 09:03:03 2016.
sphinx-quickstart on Fri Jan 12 09:05:46 2018.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to xmlschema's documentation!
=====================================
xmlschema Documentation
=======================
.. toctree::
:maxdepth: 2

6
requirements-dev.txt Normal file
View File

@ -0,0 +1,6 @@
# Requirements for setup a development environment for the xmlschema package.
setuptools
lxml
Sphinx
sphinx_rtd_theme
-e .

View File

@ -19,7 +19,7 @@ from .validators.exceptions import (
XMLSchemaParseError, XMLSchemaValidationError, XMLSchemaDecodeError,
XMLSchemaEncodeError, XMLSchemaNotBuiltError, XMLSchemaChildrenValidationError
)
from .validators.schema import XsdGlobals, XMLSchema, create_validator
from .validators.schema import XsdGlobals, XMLSchemaBase, XMLSchema, XMLSchema_v1_0, create_validator
__version__ = '0.9.18'
__author__ = "Davide Brunato"

View File

@ -81,6 +81,22 @@ class URIDict(MutableMapping):
def __repr__(self):
return repr(self._store)
def clear(self):
self._store.clear()
class NamespaceResourcesMap(URIDict):
def __setitem__(self, uri, value):
uri = self.normalize(uri)
if isinstance(value, list):
self._store[uri] = value
else:
try:
self._store[uri].append(value)
except KeyError:
self._store[uri] = [value]
class NamespaceMapper(MutableMapping):
"""

View File

@ -19,20 +19,31 @@ from .namespaces import get_namespace
from .qnames import XSI_SCHEMA_LOCATION, XSI_NONS_SCHEMA_LOCATION
def get_xsi_schema_location(elem):
"""Retrieve the attribute xsi:schemaLocation from an XML document node."""
try:
return elem.find('.[@%s]' % XSI_SCHEMA_LOCATION).attrib.get(XSI_SCHEMA_LOCATION)
except AttributeError:
return None
def iter_schema_location_hints(elem, namespace=None):
"""
Generates a sequence of location hints from xsi:schemaLocation and
xsi:noNamespaceSchemaLocation attributes of an Element object.
:param elem: An ElementTree element.
:param namespace: If not `None` limits hints to a specific namespace.
:return: Generate couples of namespace URI and resource URL.
"""
if namespace != '':
try:
locations = elem.find('.[@%s]' % XSI_SCHEMA_LOCATION).get(XSI_SCHEMA_LOCATION)
except AttributeError:
pass # elem has no xsi:schemaLocation attribute
else:
locations = locations.split()
for uri, url in zip(locations[0::2], locations[1::2]):
if namespace is None or uri == namespace:
yield uri, url
def get_xsi_no_namespace_schema_location(elem):
"""Retrieve the attribute xsi:noNamespaceSchemaLocation from an XML document node."""
try:
return elem.find('.[@%s]' % XSI_NONS_SCHEMA_LOCATION).attrib.get(XSI_NONS_SCHEMA_LOCATION)
except AttributeError:
return None
if not namespace:
try:
yield '', elem.find('.[@%s]' % XSI_NONS_SCHEMA_LOCATION).get(XSI_NONS_SCHEMA_LOCATION)
except AttributeError:
pass
def load_xml_resource(source, element_only=True):
@ -246,16 +257,10 @@ def fetch_schema(xml_document):
namespace = get_namespace(xml_root.tag)
base_url = None if xml_url is None else os.path.dirname(xml_url)
if namespace:
uri_list = get_xsi_schema_location(xml_root).split()
for uri, url in zip(uri_list[0::2], uri_list[1::2]):
if uri == namespace:
try:
return fetch_resource(url, base_url)
except XMLSchemaURLError:
pass
else:
schema_location = get_xsi_no_namespace_schema_location(xml_root)
if schema_location:
return fetch_resource(schema_location, base_url)
for uri, url in iter_schema_location_hints(xml_root, namespace):
try:
return fetch_resource(url, base_url)
except XMLSchemaURLError:
pass
raise XMLSchemaValueError("schema for XML document %r not found." % xml_document)

View File

@ -94,9 +94,11 @@ def get_args_parser():
"-v", dest="version", metavar='VERSION', type=xsd_version_number, default='1.0',
help="XSD version to use for schema (default is 1.0)."
)
parser.add_argument(
'-l', dest='locations', nargs=2, type=str, default=None, action='append'
)
return parser
test_line_parser = get_args_parser()
@ -109,6 +111,9 @@ def tests_factory(test_function_builder, pathname, label="validation", suffix="x
continue
test_args = test_line_parser.parse_args(get_test_args(line))
if test_args.locations is not None:
test_args.locations = dict(test_args.locations)
test_file = os.path.join(os.path.dirname(fileinput.filename()), test_args.filename)
if not os.path.isfile(test_file) or os.path.splitext(test_file)[1].lower() != '.%s' % suffix:
continue
@ -118,7 +123,9 @@ def tests_factory(test_function_builder, pathname, label="validation", suffix="x
else:
schema_class = xmlschema.XMLSchema
test_func = test_function_builder(test_file, schema_class, test_args.tot_errors, test_args.inspect)
test_func = test_function_builder(
test_file, schema_class, test_args.tot_errors, test_args.inspect, test_args.locations
)
test_name = os.path.join(os.path.dirname(sys.argv[0]), os.path.relpath(test_file))
test_num += 1
class_name = 'Test{0}{1:03}'.format(label.title(), test_num)

View File

@ -0,0 +1,11 @@
<?xml version="1.0"?>
<xs:schema elementFormDefault="qualified"
targetNamespace="http://www.w3.org/2001/05/XMLInfoset"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:include schemaLocation="example.xsd" />
<xs:import schemaLocation="example.xsd" />
<xs:redefine schemaLocation="example.xsd"/>
<xs:import namespace="http://missing.example.test/" />
<xs:import/>
</xs:schema>

View File

@ -12,6 +12,7 @@ collection/collection3.xml 1
decoder/decoder.xsd
decoder/data2.xml 1
derivations/complex-extensions.xsd 1
elements/test-imports.xsd
issues/issue_008/issue_008.xsd
issues/issue_008/issue_008.xml 1
issues/issue_009/issue_009.xsd 2

View File

@ -257,10 +257,10 @@ _DATA_DICT = {
}
def make_test_decoding_function(xml_file, schema_class, expected_errors=0, inspect=False):
def make_test_decoding_function(xml_file, schema_class, expected_errors=0, inspect=False, locations=None):
def test_decoding(self):
schema = xmlschema.fetch_schema(xml_file)
xs = schema_class(schema)
xs = schema_class(schema, locations=locations)
errors = []
chunks = []
for obj in xs.iter_decode(xml_file):

View File

@ -28,16 +28,16 @@ from xmlschema import XMLSchemaParseError, XMLSchemaURLError
from xmlschema.tests import SchemaObserver
def make_test_schema_function(xsd_file, schema_class, expected_errors=0, inspect=False):
def make_test_schema_function(xsd_file, schema_class, expected_errors=0, inspect=False, locations=None):
def test_schema(self):
if inspect:
SchemaObserver.clear()
# print("Run %s" % self.id())
try:
if expected_errors > 0:
xs = schema_class(xsd_file, validation='lax')
xs = schema_class(xsd_file, validation='lax', locations=locations)
else:
xs = schema_class(xsd_file)
xs = schema_class(xsd_file, locations=locations)
except (XMLSchemaParseError, XMLSchemaURLError, KeyError) as err:
num_errors = 1
errors = [str(err)]

View File

@ -29,10 +29,10 @@ except ImportError:
import xmlschema
def make_test_validation_function(xml_file, schema_class=xmlschema.XMLSchema, expected_errors=0, inspect=False):
def make_test_validation_function(xml_file, schema_class, expected_errors=0, inspect=False, locations=None):
def test_validation(self):
schema = xmlschema.fetch_schema(xml_file)
xs = schema_class(schema)
xs = schema_class(schema, locations=locations)
errors = [str(e) for e in xs.iter_errors(xml_file)]
if len(errors) != expected_errors:
raise ValueError(

View File

@ -94,7 +94,7 @@ class XsdAttribute(XsdAnnotated, ValidatorMixin):
except KeyError:
if xsd_declaration is not None:
# No 'type' attribute in declaration, parse for child local simpleType
xsd_type = self.BUILDERS.simple_type_factory(xsd_declaration, self.schema, xsd_type)
xsd_type = self._BUILDERS.simple_type_factory(xsd_declaration, self.schema, xsd_type)
else:
# Empty declaration means xsdAnySimpleType
xsd_type = self.maps.lookup_type(XSD_ANY_SIMPLE_TYPE)

View File

@ -94,14 +94,14 @@ class XsdComplexType(XsdAnnotated, ValidatorMixin):
{XSD_ATTRIBUTE_TAG, XSD_ATTRIBUTE_GROUP_TAG, XSD_ANY_ATTRIBUTE_TAG}:
#
# complexType with empty content
self.content_type = self.BUILDERS.group_class(EMPTY_SEQUENCE_ELEM, self.schema)
self.attributes = self.BUILDERS.attribute_group_class(elem, self.schema)
self.content_type = self._BUILDERS.group_class(EMPTY_SEQUENCE_ELEM, self.schema)
self.attributes = self._BUILDERS.attribute_group_class(elem, self.schema)
elif content_elem.tag in {XSD_GROUP_TAG, XSD_SEQUENCE_TAG, XSD_ALL_TAG, XSD_CHOICE_TAG}:
#
# complexType with child elements
self.content_type = self.BUILDERS.group_class(content_elem, self.schema, mixed=self.mixed)
self.attributes = self.BUILDERS.attribute_group_class(elem, self.schema)
self.content_type = self._BUILDERS.group_class(content_elem, self.schema, mixed=self.mixed)
self.attributes = self._BUILDERS.attribute_group_class(elem, self.schema)
elif content_elem.tag == XSD_SIMPLE_CONTENT_TAG:
if 'mixed' in content_elem.attrib:
@ -136,15 +136,15 @@ class XsdComplexType(XsdAnnotated, ValidatorMixin):
else:
if self.schema.validation == 'skip':
self._parse_error("unexpected tag %r for complexType content:" % content_elem.tag, self)
self.content_type = self.BUILDERS.build_any_content_group(self.schema)
self.attributes = self.BUILDERS.build_any_attribute_group(self.schema)
self.content_type = self._BUILDERS.build_any_content_group(self.schema)
self.attributes = self._BUILDERS.build_any_attribute_group(self.schema)
def _parse_derivation_elem(self, elem):
derivation_elem = self._parse_component(elem, required=False)
if getattr(derivation_elem, 'tag', None) not in (XSD_RESTRICTION_TAG, XSD_EXTENSION_TAG):
self._parse_error("restriction or extension tag expected", derivation_elem)
self.content_type = self.BUILDERS.build_any_content_group(self.schema)
self.attributes = self.BUILDERS.build_any_attribute_group(self.schema)
self.content_type = self._BUILDERS.build_any_content_group(self.schema)
self.attributes = self._BUILDERS.build_any_attribute_group(self.schema)
return
self.derivation = local_name(derivation_elem.tag)
@ -180,17 +180,17 @@ class XsdComplexType(XsdAnnotated, ValidatorMixin):
# content or a complex content with a mixed and emptiable content.
if base_type.is_simple():
self._parse_error("a complexType ancestor required: %r" % base_type, elem)
self.content_type = self.BUILDERS.build_any_content_group(self.schema)
self.attributes = self.BUILDERS.attribute_group_class(elem, self.schema)
self.content_type = self._BUILDERS.build_any_content_group(self.schema)
self.attributes = self._BUILDERS.attribute_group_class(elem, self.schema)
else:
if base_type.has_simple_content() or base_type.mixed and base_type.is_emptiable():
self.content_type = self.BUILDERS.restriction_class(elem, self.schema)
self.content_type = self._BUILDERS.restriction_class(elem, self.schema)
else:
self._parse_error("with simple content cannot restrict an empty or "
"an element-only content type ", base_type.elem)
self.content_type = self.BUILDERS.build_any_content_group(self.schema)
self.content_type = self._BUILDERS.build_any_content_group(self.schema)
self.attributes = self.BUILDERS.attribute_group_class(
self.attributes = self._BUILDERS.attribute_group_class(
elem=elem,
schema=self.schema,
derivation='restriction',
@ -207,15 +207,15 @@ class XsdComplexType(XsdAnnotated, ValidatorMixin):
if base_type.is_simple():
self.content_type = base_type
self.attributes = self.BUILDERS.attribute_group_class(elem, self.schema)
self.attributes = self._BUILDERS.attribute_group_class(elem, self.schema)
else:
if base_type.has_simple_content():
self.content_type = base_type.content_type
else:
self._parse_error("base type %r has not simple content." % base_type, elem)
self.content_type = self.BUILDERS.build_any_content_group(self.schema)
self.content_type = self._BUILDERS.build_any_content_group(self.schema)
self.attributes = self.BUILDERS.attribute_group_class(
self.attributes = self._BUILDERS.attribute_group_class(
elem=elem,
schema=self.schema,
derivation='extension',
@ -227,14 +227,14 @@ class XsdComplexType(XsdAnnotated, ValidatorMixin):
# content.
group_elem = self._parse_component(elem, required=False, strict=False)
if group_elem is not None and group_elem.tag in XSD_MODEL_GROUP_TAGS:
self.content_type = self.BUILDERS.group_class(group_elem, self.schema, mixed=self.mixed)
self.content_type = self._BUILDERS.group_class(group_elem, self.schema, mixed=self.mixed)
if self.content_type.model != base_type.content_type.model:
self._parse_error(
"content model differ from base type: %r" % base_type.content_type.model, elem
)
else:
# Empty content model
self.content_type = self.BUILDERS.group_class(elem, self.schema, mixed=self.mixed)
self.content_type = self._BUILDERS.group_class(elem, self.schema, mixed=self.mixed)
if base_type.is_element_only() and self.content_type.mixed:
self._parse_error(
@ -253,7 +253,7 @@ class XsdComplexType(XsdAnnotated, ValidatorMixin):
except AttributeError:
continue
self.attributes = self.BUILDERS.attribute_group_class(
self.attributes = self._BUILDERS.attribute_group_class(
elem=elem,
schema=self.schema,
derivation='restriction',
@ -270,13 +270,13 @@ class XsdComplexType(XsdAnnotated, ValidatorMixin):
if base_type.is_empty():
# Empty model extension: don't create a nested group.
if group_elem is not None and group_elem.tag in XSD_MODEL_GROUP_TAGS:
self.content_type = self.BUILDERS.group_class(group_elem, self.schema, mixed=self.mixed)
self.content_type = self._BUILDERS.group_class(group_elem, self.schema, mixed=self.mixed)
else:
# Empty content model
self.content_type = self.BUILDERS.group_class(elem, self.schema, mixed=self.mixed)
self.content_type = self._BUILDERS.group_class(elem, self.schema, mixed=self.mixed)
else:
dummy_elem = etree_element(XSD_SEQUENCE_TAG)
self.content_type = self.BUILDERS.group_class(dummy_elem, self.schema, mixed=self.mixed)
self.content_type = self._BUILDERS.group_class(dummy_elem, self.schema, mixed=self.mixed)
if group_elem is not None and group_elem.tag in XSD_MODEL_GROUP_TAGS:
# Illegal derivation from a simple content. Applies to both XSD 1.0 and XSD 1.1.
# For the detailed rule refer to XSD 1.1 documentation:
@ -285,7 +285,7 @@ class XsdComplexType(XsdAnnotated, ValidatorMixin):
self._parse_error("base %r is simple or has a simple content." % base_type, elem)
base_type = self.maps.lookup_type(XSD_ANY_TYPE)
xsd_group = self.BUILDERS.group_class(group_elem, self.schema, mixed=self.mixed)
xsd_group = self._BUILDERS.group_class(group_elem, self.schema, mixed=self.mixed)
self.content_type.append(base_type.content_type)
self.content_type.append(xsd_group)
@ -296,7 +296,7 @@ class XsdComplexType(XsdAnnotated, ValidatorMixin):
elif not base_type.is_simple() and not base_type.has_simple_content():
self.content_type.append(base_type.content_type)
self.attributes = self.BUILDERS.attribute_group_class(
self.attributes = self._BUILDERS.attribute_group_class(
elem=elem,
schema=self.schema,
derivation='extension',

View File

@ -94,7 +94,7 @@ class XsdElement(Sequence, XsdAnnotated, ValidatorMixin, ParticleMixin, XPathMix
try:
self.attributes = value.attributes
except AttributeError:
self.attributes = self.BUILDERS.attribute_group_class(
self.attributes = self._BUILDERS.attribute_group_class(
etree_element(XSD_ATTRIBUTE_GROUP_TAG), schema=self.schema
)
super(XsdElement, self).__setattr__(name, value)
@ -158,9 +158,9 @@ class XsdElement(Sequence, XsdAnnotated, ValidatorMixin, ParticleMixin, XPathMix
child = self._parse_component(elem, required=False, strict=False)
if child is not None:
if child.tag == XSD_COMPLEX_TYPE_TAG:
self.type = self.BUILDERS.complex_type_class(child, self.schema)
self.type = self._BUILDERS.complex_type_class(child, self.schema)
elif child.tag == XSD_SIMPLE_TYPE_TAG:
self.type = self.BUILDERS.simple_type_factory(child, self.schema)
self.type = self._BUILDERS.simple_type_factory(child, self.schema)
skip = 1
else:
self.type = self.maps.lookup_type(XSD_ANY_TYPE)

View File

@ -100,6 +100,8 @@ class XMLSchemaChildrenValidationError(XMLSchemaValidationError):
def __init__(self, validator, elem, index, expected=None):
elem_ref = qname_to_prefixed(elem.tag, validator.namespaces)
self.index = index
self.expected = expected
if index >= len(elem):
reason = "The content of element %r is not complete." % elem_ref

View File

@ -40,10 +40,10 @@ def iterchildren_by_tag(tag):
"""
Defines a generator that produce all child elements that have a specific tag.
"""
def iterfind_function(root):
for elem in root:
if elem.tag == tag:
yield elem
def iterfind_function(elem):
for e in elem:
if e.tag == tag:
yield e
iterfind_function.__name__ = str('iterfind_xsd_%ss' % '_'.join(camel_case_split(local_name(tag))).lower())
return iterfind_function
@ -149,31 +149,31 @@ class XsdGlobals(XsdBaseComponent):
def __setattr__(self, name, value):
if name == 'notations':
self.lookup_notation = self._create_lookup_function(
value, XsdNotation, **{XSD_NOTATION_TAG: self.validator.BUILDERS.notation_class}
value, XsdNotation, **{XSD_NOTATION_TAG: self.validator._BUILDERS.notation_class}
)
elif name == 'types':
self.lookup_type = self._create_lookup_function(
value, (XsdSimpleType, XsdComplexType), **{
XSD_SIMPLE_TYPE_TAG: self.validator.BUILDERS.simple_type_factory,
XSD_COMPLEX_TYPE_TAG: self.validator.BUILDERS.complex_type_class
XSD_SIMPLE_TYPE_TAG: self.validator._BUILDERS.simple_type_factory,
XSD_COMPLEX_TYPE_TAG: self.validator._BUILDERS.complex_type_class
}
)
elif name == 'attributes':
self.lookup_attribute = self._create_lookup_function(
value, XsdAttribute, **{XSD_ATTRIBUTE_TAG: self.validator.BUILDERS.attribute_class}
value, XsdAttribute, **{XSD_ATTRIBUTE_TAG: self.validator._BUILDERS.attribute_class}
)
elif name == 'attribute_groups':
self.lookup_attribute_group = self._create_lookup_function(
value, XsdAttributeGroup,
**{XSD_ATTRIBUTE_GROUP_TAG: self.validator.BUILDERS.attribute_group_class}
**{XSD_ATTRIBUTE_GROUP_TAG: self.validator._BUILDERS.attribute_group_class}
)
elif name == 'groups':
self.lookup_group = self._create_lookup_function(
value, XsdGroup, **{XSD_GROUP_TAG: self.validator.BUILDERS.group_class}
value, XsdGroup, **{XSD_GROUP_TAG: self.validator._BUILDERS.group_class}
)
elif name == 'elements':
self.lookup_element = self._create_lookup_function(
value, XsdElement, **{XSD_ELEMENT_TAG: self.validator.BUILDERS.element_class}
value, XsdElement, **{XSD_ELEMENT_TAG: self.validator._BUILDERS.element_class}
)
elif name == 'base_elements':
self.lookup_base_element = self._create_lookup_function(value, XsdElement)
@ -314,8 +314,8 @@ class XsdGlobals(XsdBaseComponent):
self.constraints.clear()
if remove_schemas:
self.namespaces = URIDict()
self.resources = URIDict()
self.namespaces.clear()
self.resources.clear()
def build(self):
"""
@ -340,7 +340,7 @@ class XsdGlobals(XsdBaseComponent):
load_xsd_groups(self.groups, not_built_schemas)
if not meta_schema.built:
meta_schema.BUILDERS.builtin_types_factory(meta_schema, self.types)
meta_schema._BUILDERS.builtin_types_factory(meta_schema, self.types)
for qname in self.notations:
self.lookup_notation(qname)
@ -356,8 +356,8 @@ class XsdGlobals(XsdBaseComponent):
self.lookup_group(qname)
# Builds element declarations inside model groups.
element_class = meta_schema.BUILDERS.element_class
group_class = meta_schema.BUILDERS.group_class
element_class = meta_schema._BUILDERS.element_class
group_class = meta_schema._BUILDERS.group_class
for xsd_global in self.iter_globals():
for obj in xsd_global.iter_components(XsdGroup):
for k in range(len(obj)):

View File

@ -249,7 +249,7 @@ class XsdGroup(MutableSequence, XsdAnnotated, ValidatorMixin, ParticleMixin):
def iter_elements(self):
for item in self:
if isinstance(item, (self.BUILDERS.element_class, XsdAnyElement)):
if isinstance(item, (self._BUILDERS.element_class, XsdAnyElement)):
yield item
elif isinstance(item, XsdGroup):
for e in item.iter_elements():

View File

@ -45,6 +45,32 @@ def get_xsd_annotation(elem):
return None
def iter_xsd_components(elem, start=0):
"""
Returns an iterator for XSD child components, excluding the annotation.
"""
counter = 0
for child in elem:
if child.tag == XSD_ANNOTATION_TAG:
if counter > 0:
raise XMLSchemaValueError("XSD annotation not allowed after the first position.")
else:
if start > 0:
start -= 1
else:
yield child
counter += 1
def has_xsd_components(elem, start=0):
try:
next(iter_xsd_components(elem, start=0))
except StopIteration:
return False
else:
return True
def get_xsd_component(elem, required=True, strict=True):
"""
Returns the first XSD component child, excluding the annotation.
@ -67,23 +93,6 @@ def get_xsd_component(elem, required=True, strict=True):
raise XMLSchemaValueError("too many XSD components")
def iter_xsd_components(elem, start=0):
"""
Returns an iterator for XSD child components, excluding the annotation.
"""
counter = 0
for child in elem:
if child.tag == XSD_ANNOTATION_TAG:
if counter > 0:
raise XMLSchemaValueError("XSD annotation not allowed after the first position.")
else:
if start > 0:
start -= 1
else:
yield child
counter += 1
def get_xsd_attribute(elem, attribute, enumeration=None, **kwargs):
"""
Get an element's attribute and throws a schema error if the attribute is absent

File diff suppressed because it is too large Load Diff

View File

@ -116,7 +116,7 @@ class XsdSimpleType(XsdAnnotated, ValidatorMixin):
@property
def admitted_facets(self):
return self.schema.FACETS
return self.schema._FACETS
@property
def final(self):
@ -355,7 +355,7 @@ class XsdAtomic(XsdSimpleType):
return XSD_FACETS.union({None})
else:
try:
return self.schema.FACETS.intersection(facets)
return self.schema._FACETS.intersection(facets)
except AttributeError:
return set(primitive_type.facets.keys()).union({None})
@ -544,7 +544,7 @@ class XsdList(XsdSimpleType):
@property
def admitted_facets(self):
return self.schema.FACETS.intersection(LIST_FACETS)
return self.schema._FACETS.intersection(LIST_FACETS)
def iter_components(self, xsd_classes=None):
if xsd_classes is None or isinstance(self, xsd_classes):
@ -682,7 +682,7 @@ class XsdUnion(XsdSimpleType):
@property
def admitted_facets(self):
return self.schema.FACETS.intersection(UNION_FACETS)
return self.schema._FACETS.intersection(UNION_FACETS)
def iter_components(self, xsd_classes=None):
if xsd_classes is None or isinstance(self, xsd_classes):
@ -792,7 +792,7 @@ class XsdAtomicRestriction(XsdAtomic):
else:
if base_type.is_complex() and base_type.admit_simple_restriction():
content_type = xsd_simple_type_factory(child, self.schema)
base_type = self.BUILDERS.complex_type_class(
base_type = self._BUILDERS.complex_type_class(
elem=elem,
schema=self.schema,
content_type=content_type,
@ -801,7 +801,7 @@ class XsdAtomicRestriction(XsdAtomic):
mixed=base_type.mixed
)
has_simple_type_child = True
elif child.tag not in self.schema.FACETS:
elif child.tag not in self.schema._FACETS:
raise XMLSchemaParseError("unexpected tag %r in restriction:" % child, self)
elif child.tag in (XSD_ENUMERATION_TAG, XSD_PATTERN_TAG):
try:

View File

@ -29,25 +29,44 @@ class XsdBaseComponent(object):
Common base class for representing XML Schema components. A concrete XSD component have
to report its validity collecting building errors and implementing the properties.
Ref: https://www.w3.org/TR/xmlschema-ref/
See: https://www.w3.org/TR/xmlschema-ref/
"""
def __init__(self):
self.errors = [] # component errors
@property
def built(self):
"""
Property that is ``True`` if schema component has been fully parsed and built, ``False`` otherwise.
"""
raise NotImplementedError
@property
def validation_attempted(self):
"""
Ref: https://www.w3.org/TR/xmlschema-1/#e-validation_attempted
Ref: https://www.w3.org/TR/2012/REC-xmlschema11-1-20120405/#e-validation_attempted
Property that returns the XSD component validation status. It can be
'full', 'partial' or 'none'.
:return: 'full', 'partial' or 'none'.
| https://www.w3.org/TR/xmlschema-1/#e-validation_attempted
| https://www.w3.org/TR/2012/REC-xmlschema11-1-20120405/#e-validation_attempted
"""
raise NotImplementedError
@property
def validity(self):
"""
Property that returns the XSD component validity. It can be valid, invalid or notKnown.
| https://www.w3.org/TR/xmlschema-1/#e-validity
| https://www.w3.org/TR/2012/REC-xmlschema11-1-20120405/#e-validity
"""
if self.errors or any([comp.errors for comp in self.iter_components()]):
return 'invalid'
elif self.built:
return 'valid'
else:
return 'notKnown'
def iter_components(self, xsd_classes=None):
"""
Returns an iterator for traversing all descendant XSD components.
@ -60,7 +79,7 @@ class XsdBaseComponent(object):
@property
def all_errors(self):
"""
Returns a list with the errors of the component and of its descendants.
A list with the errors of the XSD component and of its descendants.
"""
errors = []
for comp in self.iter_components():
@ -68,21 +87,6 @@ class XsdBaseComponent(object):
errors.extend(comp.errors)
return errors
@property
def validity(self):
"""
Ref: https://www.w3.org/TR/xmlschema-1/#e-validity
Ref: https://www.w3.org/TR/2012/REC-xmlschema11-1-20120405/#e-validity
:return: 'valid', 'invalid' or 'notKnown'.
"""
if self.errors or any([comp.errors for comp in self.iter_components()]):
return 'invalid'
elif self.built:
return 'valid'
else:
return 'notKnown'
class XsdComponent(XsdBaseComponent):
"""
@ -126,8 +130,8 @@ class XsdComponent(XsdBaseComponent):
"cannot change 'schema' attribute of %r: the actual %r has a different "
"target namespace than %r." % (self, self.schema, value)
)
self._BUILDERS = value._BUILDERS
self.target_namespace = value.target_namespace
self.BUILDERS = value.BUILDERS
self.namespaces = value.namespaces
self.maps = value.maps
super(XsdComponent, self).__setattr__(name, value)
@ -368,26 +372,30 @@ class ValidatorMixin(object):
"""
def validate(self, data, use_defaults=True):
"""
Validates XML data using the XSD component.
Validates an XML data against the XSD schema/component instance.
:param data: the data source containing the XML data. Can be a string for an \
attribute or a simple type definition, or an ElementTree's Element for \
other XSD components.
:param use_defaults: Use schema's default values for filling missing data.
:raises: :exc:`XMLSchemaValidationError` if the object is not valid.
:param data: the data source containing the XML data. For a schema can be a path \
to a file or an URI of a resource or an opened file-like object or an Element Tree \
instance or a string containing XML data. For other XSD components can be a string \
for an attribute or a simple type validators, or an ElementTree's Element otherwise.
:param use_defaults: indicates whether to use default values for filling missing data.
:raises: :exc:`XMLSchemaValidationError` if XML *data* instance is not a valid.
"""
for error in self.iter_errors(data, use_defaults=use_defaults):
raise error
def iter_errors(self, data, path=None, use_defaults=True):
"""
Creates an iterator for errors generated validating XML data with
the XSD component.
Creates an iterator for the errors generated by the validation of an XML data
against the XSD schema/component instance.
:param data: the object containing the XML data. Can be a string for an \
attribute or a simple type definition, or an ElementTree's Element for \
other XSD components.
:param path:
:param data: the data source containing the XML data. For a schema can be a path \
to a file or an URI of a resource or an opened file-like object or an Element Tree \
instance or a string containing XML data. For other XSD components can be a string \
for an attribute or a simple type validators, or an ElementTree's Element otherwise.
:param path: is an optional XPath expression that defines the parts of the document \
that have to be validated. The XPath expression considers the schema as the root element \
with global elements as its children.
:param use_defaults: Use schema's default values for filling missing data.
"""
for chunk in self.iter_decode(data, path, use_defaults=use_defaults):
@ -395,16 +403,21 @@ class ValidatorMixin(object):
yield chunk
def is_valid(self, data, use_defaults=True):
"""
Like :meth:`validate` except that do not raises an exception but returns
``True`` if the XML document is valid, ``False`` if it's invalid.
"""
error = next(self.iter_errors(data, use_defaults=use_defaults), None)
return error is None
def decode(self, data, *args, **kwargs):
"""
Decodes XML data using the XSD component.
Decodes XML data using the XSD schema/component.
:param data: the object containing the XML data. Can be a string for an \
attribute or a simple type definition, or an ElementTree's Element for \
other XSD components.
:param data: the data source containing the XML data. For a schema can be a path \
to a file or an URI of a resource or an opened file-like object or an Element Tree \
instance or a string containing XML data. For other XSD components can be a string \
for an attribute or a simple type validators, or an ElementTree's Element otherwise.
:param args: arguments that maybe passed to :func:`XMLSchema.iter_decode`.
:param kwargs: keyword arguments from the ones included in the optional \
arguments of the :func:`XMLSchema.iter_decode`.

View File

@ -830,32 +830,35 @@ class XPathMixin(object):
def iterfind(self, path, namespaces=None):
"""
Generate all matching XML Schema element declarations by path.
Generates all matching XML Schema element declarations by path.
:param path: a string having an XPath expression.
:param namespaces: an optional mapping from namespace prefix to full name.
:param path: is an XPath expression that considers the schema as the root element \
with global elements as its children.
:param namespaces: is an optional mapping from namespace prefix to full name.
:return: an iterable yielding all matching declarations in the XSD/XML order.
"""
return xsd_iterfind(self, path, namespaces or getattr(self, 'namespaces', None))
def find(self, path, namespaces=None):
"""
Find first matching XML Schema element declaration by path.
Finds the first XSD/XML element or attribute matching the path.
:param path: a string having an XPath expression.
:param path: is an XPath expression that considers the schema as the root element \
with global elements as its children.
:param namespaces: an optional mapping from namespace prefix to full name.
:return: The first matching XML Schema element declaration or None if a
matching declaration is not found.
:return: The first matching XSD/XML element or attribute or ``None`` if there is not match.
"""
return next(xsd_iterfind(self, path, namespaces or getattr(self, 'namespaces', None)), None)
def findall(self, path, namespaces=None):
"""
Find all matching XML Schema element declarations by path.
Finds all matching XSD/XML elements or attributes.
:param path: a string having an XPath expression.
:param path: is an XPath expression that considers the schema as the root element \
with global elements as its children.
:param namespaces: an optional mapping from namespace prefix to full name.
:return: A list of matching XML Schema element declarations or None if a
matching declaration is not found.
:return: a list containing all matching XSD/XML elements or attributes. An empty list \
is returned if there is no match.
"""
return list(xsd_iterfind(self, path, namespaces or getattr(self, 'namespaces', None)))