[tryton-debian-vcs] python-zeep branch upstream updated. debian/0.23.0-1-3-gd5a4b86

Mathias Behrle tryton-debian-vcs at alioth.debian.org
Tue Jun 13 12:01:03 UTC 2017


The following commit has been merged in the upstream branch:
https://alioth.debian.org/plugins/scmgit/cgi-bin/gitweb.cgi/?p=tryton/python-zeep.git;a=commitdiff;h=debian/0.23.0-1-3-gd5a4b86

commit d5a4b860ebcd1e2ccf720b6ee929af8e2539c72d
Author: Mathias Behrle <mathiasb at m9s.biz>
Date:   Mon Jun 12 18:31:41 2017 +0200

    Adding upstream version 2.1.1.
    
    Signed-off-by: Mathias Behrle <mathiasb at m9s.biz>

diff --git a/CHANGES b/CHANGES
index 95e55c4..8bbebc5 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,119 @@
+2.1.1 (2017-06-11)
+------------------
+ - Fix previous release, it contained an incorrect dependency (Mock 2.1.) due
+   to bumpversion :-(
+
+
+2.1.0 (2017-06-11)
+------------------
+ - Fix recursion error while creating the signature for a global element when
+   it references itself (via ref attribute).
+ - Update Client.create_message() to apply plugins and wsse (#465)
+ - Fix handling unknown xsi types when parsing elements using xsd:anyType (#455)
+
+
+2.0.0 (2017-05-22)
+------------------
+
+This is a major release, and contains a number of backwards incompatible
+changes to the API.
+
+ - Default values of optional elements are not set by default anymore (#423)
+ - Refactor the implementation of wsdl:arrayType too make the API more
+   pythonic (backwards incompatible).
+ - The call signature for Client.create_message() was changed. It now requires
+   the service argument:
+
+     ``Client.create_message(service, operation_name, *args, **kwargs)``
+
+ - Choice elements now only work with keyword arguments and raise an exception
+   if positional arguments are passed (#439)
+ - Implement initial multiref support for SOAP RPC (#326). This was done using
+   really good real-world tests from vstoykov (thanks!)
+ - Fix exception on empty SOAP response (#442)
+ - Fix XSD default values for boolean types (Bartek Wójcicki, #386)
+
+
+1.6.0 (2017-04-27)
+------------------
+ - Implement ValueObject.__json__ for json serialization (#258)
+ - Improve handling of unexpected elements for soap:header (#378)
+ - Accept unexpected elements in complexTypes when strict is False
+ - Fix elementFormDefault/attributeFormDefault for xsd:includes (#426)
+
+
+1.5.0 (2017-04-22)
+------------------
+ - Fix issue where values of indicators (sequence/choice/all) would
+   write to the same internal dict. (#425)
+ - Set default XML parse mode to strict as was intended (#332)
+ - Add support for pickling value objects (#417)
+ - Add explicit Nil value via ``zeep.xsd.Nil`` (#424)
+ - Add xml_huge_tree kwarg to the Client() to enable lxml's huge_tree mode,
+   this is disabled by default (#332)
+ - Add support to pass base-types to type extensions (#416)
+ - Handle wsdl errors more gracefully by disabling the invalid operations
+   instead of raising an exception (#407, #387)
+
+
+1.4.1 (2017-04-01)
+------------------
+ - The previous release (1.4.0) contained an incorrect dependency due to
+   bumpversion moving all 1.3.0 versions to 1.4.0. This fixes it.
+
+
+1.4.0 (2017-04-01)
+------------------
+ - Hardcode the xml prefix to the xml namespace as defined in the specs (#367)
+ - Fix parsing of unbound sequences within xsd choices (#380)
+ - Use logger.debug() for debug related logging (#369)
+ - Add the ``Client.raw_response`` option to let zeep return the raw
+   transport response (requests.Response) instead of trying to parse it.
+ - Handle minOccurs/maxOccurs properlhy for xsd:Group elements. This also
+   fixes a bug in the xsd:Choice handling for multiple elements (#374, #410)
+ - Fix raising XMLSyntaxError when loading invalid XML (Antanas Sinica, #396)
+
+
+1.3.0 (2017-03-14)
+------------------
+ - Add support for nested xsd:choice elements (#370)
+ - Fix unresolved elements for xsd:extension, this was a regression introduced
+   in 1.2.0 (#377)
+
+
+1.2.0 (2017-03-12)
+------------------
+ - Add flag to disable strict mode in the Client. This allows zeep to better
+   work with non standard compliant SOAP Servers. See the documentation for
+   usage and potential downsides.
+ - Minor refactor of resolving of elements for improved performance
+ - Support the SOAP 1.2 'http://www.w3.org/2003/05/soap/bindings/HTTP/'
+   transport uri (#355)
+ - Fallback to matching wsdl lookups to matching when the target namespace is
+   empty (#356)
+ - Improve the handling of xsd:includes, the default namespace of the parent
+   schema is now also used during resolving of the included schema. (#360)
+ - Properly propagate the global flag for complex types when an xsd:restriction
+   is used (#360)
+ - Filter out duplicate types and elements when dump the wsdl schema (#360)
+ - Add ``zeep.CachingClient()`` which enables the SqliteCache by default
+
+
+1.1.0 (2017-02-18)
+------------------
+ - Fix an attribute error when an complexType used xsd:anyType as base
+   restriction (#352)
+ - Update asyncio transport to return requests.Response objects (#335)
+
+
+1.0.0 (2017-01-31)
+------------------
+ - Use cgi.parse_header() to extract media_type for multipart/related checks
+   (#327)
+ - Don't ignore nil elements, instead return None when parsing xml (#328)
+ - Fix regression when using WSA with an older lxml version (#197)
+
+
 0.27.0 (2017-01-28)
 -------------------
  - Add support for SOAP attachments (multipart responses). (Dave Wapstra, #302)
@@ -11,13 +127,13 @@
 This release again introduces some backwords incompatibilties. The next release
 will hopefully be 1.0 which will introduce semver.
 
- - **backwards-incompatible**: The Transport class now accepts a 
+ - **backwards-incompatible**: The Transport class now accepts a
    ``requests.Session()`` object instead of ``http_auth`` and ``verify``. This
    allows for more flexibility.
  - **backwards-incompatible**: Zeep no longer sets a default cache backend.
    Please see http://docs.python-zeep.org/en/master/transport.html#caching for
    information about how to configure a cache.
- - Add ``zeep.xsd.SkipValue`` which instructs the serialize to ignore the 
+ - Add ``zeep.xsd.SkipValue`` which instructs the serialize to ignore the
    element.
  - Support duplicate target namespaces in the wsdl definition (#320)
  - Fix resolving element/types for xsd schema's with duplicate tns (#319)
@@ -40,12 +156,12 @@ will hopefully be 1.0 which will introduce semver.
    type. Instead log a message (#273)
  - Fix serializing etree.Element instances in the helpers.serialize function
    (#255)
- - Fix infinite loop during parsing of xsd.Sequence where max_occurs is 
+ - Fix infinite loop during parsing of xsd.Sequence where max_occurs is
    unbounded (#256)
  - Make the xsd.Element name kwarg required
- - Improve handling of the xsd:anyType element when passed instances of 
+ - Improve handling of the xsd:anyType element when passed instances of
    complexType's (#252)
- - Silently ignore unsupported binding transports instead of an hard error 
+ - Silently ignore unsupported binding transports instead of an hard error
    (#277)
  - Support microseconds for xsd.dateTime and xsd.Time (#280)
  - Don't mutate passed values to the zeep operations (#280)
@@ -56,7 +172,7 @@ will hopefully be 1.0 which will introduce semver.
  - Add Client.set_default_soapheaders() to set soapheaders which are to be used
    on all operations done via the client object.
  - Add basic support for asyncio using aiohttp. Many thanks to chrisimcevoy
-   for the initial implementation! Please see 
+   for the initial implementation! Please see
    https://github.com/mvantellingen/python-zeep/pull/207 and
    https://github.com/mvantellingen/python-zeep/pull/251 for more information
  - Fix recursion error when generating the call signature (jaceksnet, #264)
@@ -65,7 +181,7 @@ will hopefully be 1.0 which will introduce semver.
 0.22.1 (2016-11-22)
 -------------------
  - Fix reversed() error (jaceksnet) (#260)
- - Better error message when unexpected xml elements are encountered in 
+ - Better error message when unexpected xml elements are encountered in
    sequences.
 
 
@@ -74,13 +190,13 @@ will hopefully be 1.0 which will introduce semver.
  - Force the soap:address / http:address to HTTPS when the wsdl is loaded from
    a https url (#228)
  - Improvements to the xsd:union handling. The matching base class is now used
-   for serializing/deserializing the values. If there is no matching base class 
+   for serializing/deserializing the values. If there is no matching base class
    then the raw value is returned. (#195)
  - Fix handling of xsd:any with maxOccurs > 1 in xsd:choice elements (#253)
- - Add workaround for schema's importing the xsd from 
+ - Add workaround for schema's importing the xsd from
    http://www.w3.org/XML/1998/namespace (#220)
  - Add new Client.type_factory(namespace) method which returns a factory to
-   simplify creation of types. 
+   simplify creation of types.
 
 
 
@@ -88,15 +204,15 @@ will hopefully be 1.0 which will introduce semver.
 -------------------
  - Don't error on empty xml namespaces declarations in inline schema's (#186)
  - Wrap importing of sqlite3 in try..except for Google App Engine (#243)
- - Don't use pkg_resources to determine the zeep version, use __version__ 
+ - Don't use pkg_resources to determine the zeep version, use __version__
    instead (#243).
- - Fix SOAP arrays by wrapping children in the appropriate element 
+ - Fix SOAP arrays by wrapping children in the appropriate element
    (joeribekker, #236)
  - Add ``operation_timeout`` kwarg to the Transport class to set timeouts for
    operations. The default is still no timeout (#140)
  - Introduce client.options context manager to temporarily override various
    options (only timeout for now) (#140)
- - Wrap the parsing of xml values in a try..except block and log an error 
+ - Wrap the parsing of xml values in a try..except block and log an error
    instead of throwing an exception (#137)
  - Fix xsd:choice xml rendering with nested choice/sequence structure (#221)
  - Correctly resolve header elements of which the message part defines the
@@ -106,7 +222,7 @@ will hopefully be 1.0 which will introduce semver.
 0.20.0 (2016-10-24)
 -------------------
  - Major performance improvements / lower memory usage. Zeep now no longer
-   copies data and alters it in place but instead uses a set to keep track of 
+   copies data and alters it in place but instead uses a set to keep track of
    modified data.
  - Fix parsing empty soap response (#223)
  - Major refactor of the xsd:extension / xsd:restriction implementation.
@@ -118,12 +234,12 @@ will hopefully be 1.0 which will introduce semver.
 -------------------
  - **backwards-incompatible**: If the WSDL defines that the endpoint returns
    soap:header elements and/or multple soap:body messages then the return
-   signature of the operation is changed. You can now explcitly access the 
+   signature of the operation is changed. You can now explcitly access the
    body and header elements.
  - Fix parsing HTTP bindings when there are no message elements (#185)
  - Fix deserializing RPC responses (#219
  - Add support for SOAP 1.2 Fault subcodes (#210, vashek)
- - Don't alter the _soapheaders elements during rendering, instead create a 
+ - Don't alter the _soapheaders elements during rendering, instead create a
    deepcopy first. (#188)
  - Add the SOAPAction to the Content-Type header in SOAP 1.2 bindings (#211)
  - Fix issue when mixing elements and any elements in a choice type (#192)
@@ -139,7 +255,7 @@ will hopefully be 1.0 which will introduce semver.
 
 0.18.0 (2016-09-23)
 -------------------
- - Fix parsing Any elements by using the namespace map of the response node 
+ - Fix parsing Any elements by using the namespace map of the response node
    instead of the namespace map of the wsdl. (#184, #164)
  - Improve handling of nested choice elements (choice>sequence>choice)
 
@@ -149,14 +265,14 @@ will hopefully be 1.0 which will introduce semver.
  - Add support for xsd:notation (#183)
  - Add improvements to resolving phase so that all objects are resolved.
  - Improve implementation of xsd.attributeGroup and xsd.UniqueType
- - Create a deepcopy of the args and kwargs passed to objects so that the 
+ - Create a deepcopy of the args and kwargs passed to objects so that the
    original are unmodified.
  - Improve handling of wsdl:arrayType
 
 
 0.16.0 (2016-09-06)
 -------------------
- - Fix error when rendering choice elements with have sequences as children, 
+ - Fix error when rendering choice elements with have sequences as children,
    see #150
  - Re-use credentials passed to python -mzeep <wsdl> (#130)
  - Workaround invalid usage of qualified vs non-qualified element tags in the
@@ -183,7 +299,7 @@ will hopefully be 1.0 which will introduce semver.
 0.14.0 (2016-08-03)
 -------------------
  - Global attributes are now always correctly handled as qualified. (#129)
- - Fix parsing xml data containing simpleContent types (#136). 
+ - Fix parsing xml data containing simpleContent types (#136).
  - Set xsi:nil attribute when serializing objects to xml (#141)
  - Fix rendering choice elements when the element is mixed with other elements
    in a sequence (#150)
@@ -199,8 +315,8 @@ will hopefully be 1.0 which will introduce semver.
    exception. This better matches with what lxml does.
  - **backwards-incompatible**: The ``persistent`` kwarg is removed from the
    SqliteCache.__init__() call. Use the new InMemoryCache() instead when you
-   don't want to persist data. This was required to make the SqliteCache 
-   backend thread-safe since we now open/close the db when writing/reading 
+   don't want to persist data. This was required to make the SqliteCache
+   backend thread-safe since we now open/close the db when writing/reading
    from it (with an additional lock).
  - Fix zeep.helpers.serialize_object() for nested objects (#123)
  - Remove fallback between soap 1.1 and soap 1.2 namespaces during the parsing
@@ -209,30 +325,30 @@ will hopefully be 1.0 which will introduce semver.
 
 0.12.0 (2016-07-09)
 -------------------
- - **backwards-incompatible**: Choice elements are now unwrapped if 
+ - **backwards-incompatible**: Choice elements are now unwrapped if
    maxOccurs=1. This results in easier operation definitions when choices are
-   used. 
+   used.
  - **backwards-incompatible**: The _soapheader kwarg is renamed to _soapheaders
    and now requires a nested dictionary with the header name as key or a list
-   of values (value object or lxml.etree.Element object). Please see the 
+   of values (value object or lxml.etree.Element object). Please see the
    call signature of the function using ``python -mzeep <wsdl>``.
  - Support the element ref's to xsd:schema elements.
  - Improve the signature() output of element and type definitions
  - Accept lxml.etree.Element objects as value for Any elements.
  - And various other fixes
- 
+
 
 0.11.0 (2016-07-03)
 -------------------
  - **backwards-incompatible**: The kwarg name for Any and Choice elements are
    renamed to generic ``_value_N`` names.
- - **backwards-incompatible**: Client.set_address() is replaced with the 
+ - **backwards-incompatible**: Client.set_address() is replaced with the
    Client.create_service() call
  - Auto-load the http://schemas.xmlsoap.org/soap/encoding/ schema if it is
    referenced but not imported. Too many XSD's assume that the schema is always
    available.
- - Major refactoring of the XSD handling to correctly support nested 
-   xsd:sequence elements. 
+ - Major refactoring of the XSD handling to correctly support nested
+   xsd:sequence elements.
  - Add ``logger.debug()`` calls around Transport.post() to allow capturing the
    content send/received from the server
  - Add proper support for default values on attributes and elements.
@@ -240,12 +356,12 @@ will hopefully be 1.0 which will introduce semver.
 
 0.10.0 (2016-06-22)
 -------------------
- - Make global elements / types truly global by refactoring the Schema 
+ - Make global elements / types truly global by refactoring the Schema
    parsing. Previously the lookups where non-transitive, but this should only
    be the case during parsing of the xml schema.
  - Properly unwrap XML responses in soap.DocumentMessage when a choice is the
    root element. (#80)
- - Update exceptions structure, all zeep exceptions are now using 
+ - Update exceptions structure, all zeep exceptions are now using
    zeep.exceptions.Error() as base class.
 
 
@@ -253,12 +369,12 @@ will hopefully be 1.0 which will introduce semver.
 ------------------
  - Quote the SOAPAction header value (Derek Harland)
  - Undo fallback for SOAPAction if it is empty (#83)
- 
+
 
 0.9.0 (2016-06-14)
 ------------------
  - Use the appdirs module to retrieve the OS cache path. Note that this results
-   in an other default cache path then previous releases! See 
+   in an other default cache path then previous releases! See
    https://github.com/ActiveState/appdirs for more information.
  - Fix regression when initializing soap objects with invalid kwargs.
  - Update wsse.UsernameToken to set encoding type on nonce (Antonio Cuni)
@@ -273,7 +389,7 @@ will hopefully be 1.0 which will introduce semver.
 
 0.8.1 (2016-06-08)
 ------------------
- - Use the operation name for the xml element which wraps the parameters in 
+ - Use the operation name for the xml element which wraps the parameters in
    for soap RPC messages (#60)
 
 
@@ -281,7 +397,7 @@ will hopefully be 1.0 which will introduce semver.
 ------------------
  - Add ability to override the soap endpoint via `Client.set_address()`
  - Fix parsing ComplexTypes which have no child elements (#50)
- - Handle xsi:type attributes on anyType's correctly when deserializing 
+ - Handle xsi:type attributes on anyType's correctly when deserializing
    responses (#17)
  - Fix xsd:restriction on xsd:simpleType's when the base type wasn't defined
    yet. (#59)
@@ -298,9 +414,9 @@ will hopefully be 1.0 which will introduce semver.
 ------------------
  - Add support HTTP authentication (mcordes). This adds a new attribute to the
    Transport client() which passes the http_auth value to requests. (#31)
- - Fix issue where setting cache=None to Transport class didn't disable 
+ - Fix issue where setting cache=None to Transport class didn't disable
    caching.
- - Refactor handling of wsdl:imports, don't merge definitions but instead 
+ - Refactor handling of wsdl:imports, don't merge definitions but instead
    lookup values in child definitions. (#40)
  - Remove unused namespace declarations from the generated SOAP messages.
  - Update requirement of six>=1.0.0 to six>=1.9.0 (#39)
@@ -323,14 +439,14 @@ will hopefully be 1.0 which will introduce semver.
 ------------------
  - Handle attributes during parsing of the response values>
  - Don't create empty soap objects when the root element is empty.
- - Implement support for WSSE usernameToken profile including 
+ - Implement support for WSSE usernameToken profile including
    passwordText/passwordDigest.
  - Improve XSD date/time related builtins.
- - Various minor XSD handling fixes 
+ - Various minor XSD handling fixes
  - Use the correct soap-envelope XML namespace for the Soap 1.2 binding
  - Use `application/soap+xml` as content-type in the Soap 1.2 binding
- - **backwards incompatible**: Make cache part of the transport object 
-   instead of the client.  This changes the call signature of the Client() 
+ - **backwards incompatible**: Make cache part of the transport object
+   instead of the client.  This changes the call signature of the Client()
    class.  (Marek Wywiał)
  - Add the `verify` kwarg to the Transport object to disable ssl certificate
    verification. (Marek Wywiał)
@@ -361,7 +477,7 @@ will hopefully be 1.0 which will introduce semver.
 ------------------
  - Improve xsd.DateTime, xsd.Date and xsd.Time implementations by using the
    isodate module.
- - Implement xsd.Duration 
+ - Implement xsd.Duration
 
 
 0.2.3 (2016-04-03)
diff --git a/CONTRIBUTORS.rst b/CONTRIBUTORS.rst
new file mode 100644
index 0000000..44a9546
--- /dev/null
+++ b/CONTRIBUTORS.rst
@@ -0,0 +1,45 @@
+Authors
+=======
+* Michael van Tellingen
+
+Contributors
+============
+* vashek
+* Marco Vellinga
+* jaceksnet
+* Andrew Serong
+* Joeri Bekker
+* Eric Wong
+* Jacek Stępniewski
+* Alexey Stepanov
+* Julien Delasoie
+* bjarnagin
+* mcordes
+* Sam Denton
+* David Baumgold
+* fiebiga
+* Antonio Cuni
+* Alexandre de Mari
+* Jason Vertrees
+* Nicolas Evrard
+* Matt Grimm (mgrimm)
+* Marek Wywiał
+* Falldog
+* btmanm
+* Caleb Salt
+* Julien Marechal
+* Mike Fiedler
+* Dave Wapstra
+* OrangGeeGee
+* Stefano Parmesan
+* Jan Murre
+* Ben Tucker
+* Bruno Duyé
+* Christoph Heuel
+* Derek Harland
+* Eric Waller
+* Falk Schuetzenmeister
+* Jon Jenkins
+* Raymond Piller
+* Zoltan Benedek
+* Øyvind Heddeland Instefjord
diff --git a/LICENSE b/LICENSE
index 75eecda..1229d20 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 The MIT License (MIT)
 
-Copyright (c) 2016 Michael van Tellingen
+Copyright (c) 2016-2017 Michael van Tellingen
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..2f6e6ce
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,19 @@
+# Exclude everything by default
+exclude *
+recursive-exclude * *
+
+include MANIFEST.in
+include CHANGES
+include CONTRIBUTORS.rst
+include LICENSE
+include README.rst
+include setup.cfg
+include setup.py
+
+graft examples
+graft src
+graft tests
+
+global-exclude __pycache__
+global-exclude *.py[co]
+global-exclude .DS_Store
diff --git a/PKG-INFO b/PKG-INFO
index 0dcc777..6f21657 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: zeep
-Version: 0.27.0
+Version: 2.1.1
 Summary: A modern/fast Python SOAP client based on lxml / requests
 Home-page: http://docs.python-zeep.org
 Author: Michael van Tellingen
@@ -57,18 +57,16 @@ Description: ========================
         Support
         =======
         
-        If you encounter bugs then please `let me know`_ .  A copy of the WSDL file if
-        possible would be most helpful. 
+        If you want to report a bug then please first read 
+        http://docs.python-zeep.org/en/master/reporting_bugs.html
         
         I'm also able to offer commercial support.  As in contracting work. Please
-        contact me at info at mvantellingen.nl for more information. If you just have a
-        random question and don't intent to actually pay me for my support then please
-        DO NOT email me at that e-mail address but just use stackoverflow or something..
-        
-        .. _let me know: https://github.com/mvantellingen/python-zeep/issues
+        contact me at info at mvantellingen.nl for more information.  Note that asking 
+        questions or reporting bugs via this e-mail address will be ignored. Pleae use
+        the appropriate channels for that (e.g. stackoverflow)
         
 Platform: UNKNOWN
-Classifier: Development Status :: 4 - Beta
+Classifier: Development Status :: 5 - Production/Stable
 Classifier: License :: OSI Approved :: MIT License
 Classifier: Programming Language :: Python :: 2
 Classifier: Programming Language :: Python :: 2.7
diff --git a/README.rst b/README.rst
index c71c9c6..647b78c 100644
--- a/README.rst
+++ b/README.rst
@@ -72,12 +72,10 @@ information.
 Support
 =======
 
-If you encounter bugs then please `let me know`_ .  A copy of the WSDL file if
-possible would be most helpful. 
+If you want to report a bug then please first read 
+http://docs.python-zeep.org/en/master/reporting_bugs.html
 
 I'm also able to offer commercial support.  As in contracting work. Please
-contact me at info at mvantellingen.nl for more information. If you just have a
-random question and don't intent to actually pay me for my support then please
-DO NOT email me at that e-mail address but just use stackoverflow or something..
-
-.. _let me know: https://github.com/mvantellingen/python-zeep/issues
+contact me at info at mvantellingen.nl for more information.  Note that asking 
+questions or reporting bugs via this e-mail address will be ignored. Pleae use
+the appropriate channels for that (e.g. stackoverflow)
diff --git a/examples/http_basic_auth.py b/examples/http_basic_auth.py
index e48f6b5..0fe84dd 100644
--- a/examples/http_basic_auth.py
+++ b/examples/http_basic_auth.py
@@ -1,10 +1,16 @@
 from __future__ import print_function
+
+from requests import Session
+from requests.auth import HTTPBasicAuth
+
 import zeep
 from zeep.transports import Transport
 
 # Example using basic authentication with a webservice
 
-transport_with_basic_auth = Transport(http_auth=('username', 'password'))
+session = Session()
+session.auth = HTTPBasicAuth('username', 'password')
+transport_with_basic_auth = Transport(session=session)
 
 client = zeep.Client(
     wsdl='http://nonexistent?WSDL',
diff --git a/setup.cfg b/setup.cfg
index 3128c8e..e77a765 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 0.27.0
+current_version = 2.1.1
 commit = true
 tag = true
 tag_name = {new_version}
@@ -19,6 +19,8 @@ max-line-length = 99
 
 [bumpversion:file:docs/conf.py]
 
+[bumpversion:file:docs/index.rst]
+
 [bumpversion:file:src/zeep/__init__.py]
 
 [coverage:run]
@@ -38,5 +40,4 @@ show_missing = True
 [egg_info]
 tag_build = 
 tag_date = 0
-tag_svn_revision = 0
 
diff --git a/setup.py b/setup.py
index d75137a..dff575e 100755
--- a/setup.py
+++ b/setup.py
@@ -1,15 +1,16 @@
 import re
+import sys
 
 from setuptools import find_packages, setup
 
 install_requires = [
     'appdirs>=1.4.0',
-    'cached-property>=1.0.0',
+    'cached-property>=1.3.0',
     'defusedxml>=0.4.1',
     'isodate>=0.5.4',
     'lxml>=3.0.0',
     'requests>=2.7.0',
-    'requests-toolbelt>=0.7.0',
+    'requests-toolbelt>=0.7.1',
     'six>=1.9.0',
     'pytz',
 ]
@@ -18,9 +19,7 @@ docs_require = [
     'sphinx>=1.4.0',
 ]
 
-async_require = [
-    'aiohttp>=1.0',
-]
+async_require = []  # see below
 
 xmlsec_require = [
     'xmlsec>=0.6.1',
@@ -41,13 +40,19 @@ tests_require = [
     'flake8-debugger==1.4.0',
 ]
 
+
+if sys.version_info > (3, 4, 2):
+    async_require.append('aiohttp>=1.0')
+    tests_require.append('aioresponses>=0.1.3')
+
+
 with open('README.rst') as fh:
     long_description = re.sub(
         '^.. start-no-pypi.*^.. end-no-pypi', '', fh.read(), flags=re.M | re.S)
 
 setup(
     name='zeep',
-    version='0.27.0',
+    version='2.1.1',
     description='A modern/fast Python SOAP client based on lxml / requests',
     long_description=long_description,
     author="Michael van Tellingen",
@@ -69,7 +74,7 @@ setup(
 
     license='MIT',
     classifiers=[
-        'Development Status :: 4 - Beta',
+        'Development Status :: 5 - Production/Stable',
         'License :: OSI Approved :: MIT License',
         'Programming Language :: Python :: 2',
         'Programming Language :: Python :: 2.7',
diff --git a/src/zeep.egg-info/PKG-INFO b/src/zeep.egg-info/PKG-INFO
index 0dcc777..6f21657 100644
--- a/src/zeep.egg-info/PKG-INFO
+++ b/src/zeep.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: zeep
-Version: 0.27.0
+Version: 2.1.1
 Summary: A modern/fast Python SOAP client based on lxml / requests
 Home-page: http://docs.python-zeep.org
 Author: Michael van Tellingen
@@ -57,18 +57,16 @@ Description: ========================
         Support
         =======
         
-        If you encounter bugs then please `let me know`_ .  A copy of the WSDL file if
-        possible would be most helpful. 
+        If you want to report a bug then please first read 
+        http://docs.python-zeep.org/en/master/reporting_bugs.html
         
         I'm also able to offer commercial support.  As in contracting work. Please
-        contact me at info at mvantellingen.nl for more information. If you just have a
-        random question and don't intent to actually pay me for my support then please
-        DO NOT email me at that e-mail address but just use stackoverflow or something..
-        
-        .. _let me know: https://github.com/mvantellingen/python-zeep/issues
+        contact me at info at mvantellingen.nl for more information.  Note that asking 
+        questions or reporting bugs via this e-mail address will be ignored. Pleae use
+        the appropriate channels for that (e.g. stackoverflow)
         
 Platform: UNKNOWN
-Classifier: Development Status :: 4 - Beta
+Classifier: Development Status :: 5 - Production/Stable
 Classifier: License :: OSI Approved :: MIT License
 Classifier: Programming Language :: Python :: 2
 Classifier: Programming Language :: Python :: 2.7
diff --git a/src/zeep.egg-info/SOURCES.txt b/src/zeep.egg-info/SOURCES.txt
index af3d3dd..ab2e621 100644
--- a/src/zeep.egg-info/SOURCES.txt
+++ b/src/zeep.egg-info/SOURCES.txt
@@ -1,5 +1,7 @@
 CHANGES
+CONTRIBUTORS.rst
 LICENSE
+MANIFEST.in
 README.rst
 setup.cfg
 setup.py
@@ -16,8 +18,8 @@ src/zeep/cache.py
 src/zeep/client.py
 src/zeep/exceptions.py
 src/zeep/helpers.py
+src/zeep/loader.py
 src/zeep/ns.py
-src/zeep/parser.py
 src/zeep/plugins.py
 src/zeep/transports.py
 src/zeep/utils.py
@@ -45,6 +47,7 @@ src/zeep/wsdl/messages/__init__.py
 src/zeep/wsdl/messages/base.py
 src/zeep/wsdl/messages/http.py
 src/zeep/wsdl/messages/mime.py
+src/zeep/wsdl/messages/multiref.py
 src/zeep/wsdl/messages/soap.py
 src/zeep/wsse/__init__.py
 src/zeep/wsse/compose.py
@@ -74,6 +77,7 @@ src/zeep/xsd/types/builtins.py
 src/zeep/xsd/types/collection.py
 src/zeep/xsd/types/complex.py
 src/zeep/xsd/types/simple.py
+src/zeep/xsd/types/unresolved.py
 tests/__init__.py
 tests/cert_valid.pem
 tests/cert_valid_pw.pem
@@ -83,9 +87,11 @@ tests/test_cache.py
 tests/test_client.py
 tests/test_client_factory.py
 tests/test_helpers.py
+tests/test_loader.py
 tests/test_main.py
 tests/test_pprint.py
 tests/test_response.py
+tests/test_soap_multiref.py
 tests/test_transports.py
 tests/test_wsa.py
 tests/test_wsdl.py
@@ -101,9 +107,12 @@ tests/test_xsd.py
 tests/test_xsd_any.py
 tests/test_xsd_attributes.py
 tests/test_xsd_builtins.py
-tests/test_xsd_choice.py
 tests/test_xsd_complex_types.py
 tests/test_xsd_extension.py
+tests/test_xsd_indicators_all.py
+tests/test_xsd_indicators_choice.py
+tests/test_xsd_indicators_group.py
+tests/test_xsd_indicators_sequence.py
 tests/test_xsd_integration.py
 tests/test_xsd_parse.py
 tests/test_xsd_schemas.py
diff --git a/src/zeep.egg-info/requires.txt b/src/zeep.egg-info/requires.txt
index fe4a79f..bbcba39 100644
--- a/src/zeep.egg-info/requires.txt
+++ b/src/zeep.egg-info/requires.txt
@@ -1,10 +1,10 @@
 appdirs>=1.4.0
-cached-property>=1.0.0
+cached-property>=1.3.0
 defusedxml>=0.4.1
 isodate>=0.5.4
 lxml>=3.0.0
 requests>=2.7.0
-requests-toolbelt>=0.7.0
+requests-toolbelt>=0.7.1
 six>=1.9.0
 pytz
 
@@ -25,6 +25,7 @@ isort==4.2.5
 flake8==3.2.1
 flake8-blind-except==0.1.1
 flake8-debugger==1.4.0
+aioresponses>=0.1.3
 
 [xmlsec]
 xmlsec>=0.6.1
diff --git a/src/zeep/__init__.py b/src/zeep/__init__.py
index 0cb7e04..faa5a67 100644
--- a/src/zeep/__init__.py
+++ b/src/zeep/__init__.py
@@ -1,6 +1,6 @@
-from zeep.client import Client  # noqa
+from zeep.client import CachingClient, Client  # noqa
 from zeep.transports import Transport  # noqa
 from zeep.plugins import Plugin  # noqa
 from zeep.xsd.valueobjects import AnyObject  # noqa
 
-__version__ = '0.27.0'
+__version__ = '2.1.1'
diff --git a/src/zeep/__main__.py b/src/zeep/__main__.py
index ff3d509..72afadd 100644
--- a/src/zeep/__main__.py
+++ b/src/zeep/__main__.py
@@ -7,6 +7,7 @@ import time
 
 import requests
 from six.moves.urllib.parse import urlparse
+
 from zeep.cache import SqliteCache
 from zeep.client import Client
 from zeep.transports import Transport
@@ -27,6 +28,9 @@ def parse_arguments(args=None):
         '--verbose', action='store_true', help='Enable verbose output')
     parser.add_argument(
         '--profile', help="Enable profiling and save output to given file")
+    parser.add_argument(
+        '--no-strict', action='store_true', default=False,
+        help="Disable strict mode")
     return parser.parse_args(args)
 
 
@@ -72,7 +76,9 @@ def main(args):
 
     transport = Transport(cache=cache, session=session)
     st = time.time()
-    client = Client(args.wsdl_file, transport=transport)
+
+    strict = not args.no_strict
+    client = Client(args.wsdl_file, transport=transport, strict=strict)
     logger.debug("Loading WSDL took %sms", (time.time() - st) * 1000)
 
     if args.profile:
diff --git a/src/zeep/asyncio/transport.py b/src/zeep/asyncio/transport.py
index 6968c2a..ec3cc40 100644
--- a/src/zeep/asyncio/transport.py
+++ b/src/zeep/asyncio/transport.py
@@ -6,6 +6,8 @@ import asyncio
 import logging
 
 import aiohttp
+from requests import Response
+
 from zeep.transports import Transport
 from zeep.utils import get_version
 from zeep.wsdl.utils import etree_to_string
@@ -27,9 +29,14 @@ class AsyncTransport(Transport):
         self.logger = logging.getLogger(__name__)
 
         self.session = session or aiohttp.ClientSession(loop=self.loop)
+        self._close_session = session is None
         self.session._default_headers['User-Agent'] = (
             'Zeep/%s (www.python-zeep.org)' % (get_version()))
 
+    def __del__(self):
+        if self._close_session:
+            self.session.close()
+
     def _load_remote_data(self, url):
         result = None
 
@@ -56,20 +63,21 @@ class AsyncTransport(Transport):
     async def post_xml(self, address, envelope, headers):
         message = etree_to_string(envelope)
         response = await self.post(address, message, headers)
-
-        from pretend import stub
-        return stub(
-            content=await response.read(),
-            status_code=response.status,
-            headers=response.headers)
+        return await self.new_response(response)
 
     async def get(self, address, params, headers):
         with aiohttp.Timeout(self.operation_timeout):
             response = await self.session.get(
                 address, params=params, headers=headers)
 
-            from pretend import stub
-            return await stub(
-                content=await response.read(),
-                status_code=response.status,
-                headers=response.headers)
+            return await self.new_response(response)
+
+    async def new_response(self, response):
+        """Convert an aiohttp.Response object to a requests.Response object"""
+        new = Response()
+        new._content = await response.read()
+        new.status_code = response.status
+        new.headers = response.headers
+        new.cookies = response.cookies
+        new.encoding = response.charset
+        return new
diff --git a/src/zeep/client.py b/src/zeep/client.py
index 9c7f30a..cad16ac 100644
--- a/src/zeep/client.py
+++ b/src/zeep/client.py
@@ -4,7 +4,7 @@ from contextlib import contextmanager
 
 from zeep.transports import Transport
 from zeep.wsdl import Document
-
+from zeep.xsd.const import NotSet
 
 logger = logging.getLogger(__name__)
 
@@ -15,6 +15,12 @@ class OperationProxy(object):
         self._op_name = operation_name
 
     def __call__(self, *args, **kwargs):
+        """Call the operation with the given args and kwargs.
+
+        :rtype: zeep.xsd.CompoundValue
+
+        """
+
         if self._proxy._client._default_soapheaders:
             op_soapheaders = kwargs.get('_soapheaders')
             if op_soapheaders:
@@ -42,9 +48,19 @@ class ServiceProxy(object):
         self._binding = binding
 
     def __getattr__(self, key):
+        """Return the OperationProxy for the given key.
+
+        :rtype: OperationProxy()
+
+        """
         return self[key]
 
     def __getitem__(self, key):
+        """Return the OperationProxy for the given key.
+
+        :rtype: OperationProxy()
+
+        """
         try:
             self._binding.get(key)
         except ValueError:
@@ -62,9 +78,19 @@ class Factory(object):
             self._ns = types.get_ns_prefix(namespace)
 
     def __getattr__(self, key):
+        """Return the complexType or simpleType for the given localname.
+
+        :rtype: zeep.xsd.ComplexType or zeep.xsd.AnySimpleType
+
+        """
         return self[key]
 
     def __getitem__(self, key):
+        """Return the complexType or simpleType for the given localname.
+
+        :rtype: zeep.xsd.ComplexType or zeep.xsd.AnySimpleType
+
+        """
         return self._method('{%s}%s' % (self._ns, key))
 
 
@@ -81,19 +107,26 @@ class Client(object):
                       first port defined in the service element in the WSDL
                       document.
     :param plugins: a list of Plugin instances
+    :param xml_huge_tree: disable lxml/libxml2 security restrictions and
+                          support very deep trees and very long text content
 
 
     """
 
     def __init__(self, wsdl, wsse=None, transport=None,
-                 service_name=None, port_name=None, plugins=None):
+                 service_name=None, port_name=None, plugins=None,
+                 strict=True, xml_huge_tree=False):
         if not wsdl:
             raise ValueError("No URL given for the wsdl")
 
-        self.transport = transport or Transport()
-        self.wsdl = Document(wsdl, self.transport)
+        self.transport = transport if transport is not None else Transport()
+        self.wsdl = Document(wsdl, self.transport, strict=strict)
         self.wsse = wsse
         self.plugins = plugins if plugins is not None else []
+        self.xml_huge_tree = xml_huge_tree
+
+        # options
+        self.raw_response = False
 
         self._default_service = None
         self._default_service_name = service_name
@@ -102,7 +135,11 @@ class Client(object):
 
     @property
     def service(self):
-        """The default ServiceProxy instance"""
+        """The default ServiceProxy instance
+
+        :rtype: ServiceProxy
+
+        """
         if self._default_service:
             return self._default_service
 
@@ -116,7 +153,7 @@ class Client(object):
         return self._default_service
 
     @contextmanager
-    def options(self, timeout):
+    def options(self, timeout=NotSet, raw_response=NotSet):
         """Context manager to temporarily overrule various options.
 
         :param timeout: Set the timeout for POST/GET operations (not used for
@@ -130,8 +167,22 @@ class Client(object):
 
 
         """
-        with self.transport._options(timeout=timeout):
-            yield
+        # Store current options
+        old_raw_raw_response = self.raw_response
+
+        # Set new options
+        self.raw_response = raw_response
+
+        if timeout is not NotSet:
+            timeout_ctx = self.transport._options(timeout=timeout)
+            timeout_ctx.__enter__()
+
+        yield
+
+        self.raw_response = old_raw_raw_response
+
+        if timeout is not NotSet:
+            timeout_ctx.__exit__(None, None, None)
 
     def bind(self, service_name=None, port_name=None):
         """Create a new ServiceProxy for the given service_name and port_name.
@@ -163,15 +214,14 @@ class Client(object):
                 "are: %s" % (', '.join(self.wsdl.bindings.keys())))
         return ServiceProxy(self, binding, address=address)
 
-    def create_message(self, operation, service_name=None, port_name=None,
-                       args=None, kwargs=None):
-        """Create the payload for the given operation."""
-        service = self._get_service(service_name)
-        port = self._get_port(service, port_name)
+    def create_message(self, service, operation_name, *args, **kwargs):
+        """Create the payload for the given operation.
 
-        args = args or tuple()
-        kwargs = kwargs or {}
-        envelope, http_headers = port.binding._create(operation, args, kwargs)
+        :rtype: lxml.etree._Element
+
+        """
+        envelope, http_headers = service._binding._create(
+            operation_name, args, kwargs, client=self)
         return envelope
 
     def type_factory(self, namespace):
@@ -182,15 +232,25 @@ class Client(object):
             factory = client.type_factory('ns0')
             user = factory.User(name='John')
 
+        :rtype: Factory
+
         """
         return Factory(self.wsdl.types, 'type', namespace)
 
     def get_type(self, name):
-        """Return the type for the given qualified name."""
+        """Return the type for the given qualified name.
+
+        :rtype: zeep.xsd.ComplexType or zeep.xsd.AnySimpleType
+
+        """
         return self.wsdl.types.get_type(name)
 
     def get_element(self, name):
-        """Return the element for the given qualified name."""
+        """Return the element for the given qualified name.
+
+        :rtype: zeep.xsd.Element
+
+        """
         return self.wsdl.types.get_element(name)
 
     def set_ns_prefix(self, prefix, namespace):
@@ -227,3 +287,20 @@ class Client(object):
         else:
             service = next(iter(self.wsdl.services.values()), None)
         return service
+
+
+class CachingClient(Client):
+    """Shortcut to create a caching client, for the lazy people.
+
+    This enables the SqliteCache by default in the transport as was the default
+    in earlier versions of zeep.
+
+    """
+    def __init__(self, *args, **kwargs):
+
+        # Don't use setdefault since we want to lazily init the Transport cls
+        from zeep.cache import SqliteCache
+        kwargs['transport'] = (
+            kwargs.get('transport') or Transport(cache=SqliteCache()))
+
+        super(CachingClient, self).__init__(*args, **kwargs)
diff --git a/src/zeep/exceptions.py b/src/zeep/exceptions.py
index 0056057..a7a4c1d 100644
--- a/src/zeep/exceptions.py
+++ b/src/zeep/exceptions.py
@@ -39,7 +39,11 @@ class TransportError(Error):
 
 
 class LookupError(Error):
-    pass
+    def __init__(self, *args, **kwargs):
+        self.qname = kwargs.pop('qname', None)
+        self.item_name = kwargs.pop('item_name', None)
+        self.location = kwargs.pop('location', None)
+        super(LookupError, self).__init__(*args, **kwargs)
 
 
 class NamespaceError(Error):
@@ -74,3 +78,11 @@ class ValidationError(Error):
 
 class SignatureVerificationFailed(Error):
     pass
+
+
+class IncompleteMessage(Error):
+    pass
+
+
+class IncompleteOperation(Error):
+    pass
diff --git a/src/zeep/loader.py b/src/zeep/loader.py
new file mode 100644
index 0000000..58ea81e
--- /dev/null
+++ b/src/zeep/loader.py
@@ -0,0 +1,113 @@
+import os.path
+
+from defusedxml.lxml import fromstring
+from lxml import etree
+from six.moves.urllib.parse import urljoin, urlparse
+
+from zeep.exceptions import XMLSyntaxError
+
+
+class ImportResolver(etree.Resolver):
+    """Custom lxml resolve to use the transport object"""
+    def __init__(self, transport):
+        self.transport = transport
+
+    def resolve(self, url, pubid, context):
+        if urlparse(url).scheme in ('http', 'https'):
+            content = self.transport.load(url)
+            return self.resolve_string(content, context)
+
+
+def parse_xml(content, transport, base_url=None, strict=True,
+              xml_huge_tree=False):
+    """Parse an XML string and return the root Element.
+
+    :param content: The XML string
+    :type content: str
+    :param transport: The transport instance to load imported documents
+    :type transport: zeep.transports.Transport
+    :param base_url: The base url of the document, used to make relative
+      lookups absolute.
+    :type base_url: str
+    :param strict: boolean to indicate if the lxml should be parsed a 'strict'.
+      If false then the recover mode is enabled which tries to parse invalid
+      XML as best as it can.
+    :param xml_huge_tree: boolean to indicate if lxml should process very
+      large XML content.
+    :type strict: boolean
+    :returns: The document root
+    :rtype: lxml.etree._Element
+
+    """
+    recover = not strict
+    parser = etree.XMLParser(remove_comments=True, resolve_entities=False,
+                             recover=recover, huge_tree=xml_huge_tree)
+    parser.resolvers.add(ImportResolver(transport))
+    try:
+        return fromstring(content, parser=parser, base_url=base_url)
+    except etree.XMLSyntaxError as exc:
+        raise XMLSyntaxError("Invalid XML content received (%s)" % exc.msg)
+
+
+def load_external(url, transport, base_url=None, strict=True):
+    """Load an external XML document.
+
+    :param url:
+    :param transport:
+    :param base_url:
+    :param strict: boolean to indicate if the lxml should be parsed a 'strict'.
+      If false then the recover mode is enabled which tries to parse invalid
+      XML as best as it can.
+    :type strict: boolean
+
+    """
+    if hasattr(url, 'read'):
+        content = url.read()
+    else:
+        if base_url:
+            url = absolute_location(url, base_url)
+        content = transport.load(url)
+    return parse_xml(content, transport, base_url, strict=strict)
+
+
+def absolute_location(location, base):
+    """Make an url absolute (if it is optional) via the passed base url.
+
+    :param location: The (relative) url
+    :type location: str
+    :param base: The base location
+    :type base: str
+    :returns: An absolute URL
+    :rtype: str
+
+    """
+    if location == base:
+        return location
+
+    if urlparse(location).scheme in ('http', 'https', 'file'):
+        return location
+
+    if base and urlparse(base).scheme in ('http', 'https', 'file'):
+        return urljoin(base, location)
+    else:
+        if os.path.isabs(location):
+            return location
+        if base:
+            return os.path.realpath(
+                os.path.join(os.path.dirname(base), location))
+    return location
+
+
+def is_relative_path(value):
+    """Check if the given value is a relative path
+
+    :param value: The value
+    :type value: str
+    :returns: Boolean indicating if the url is relative. If it is absolute then
+      False is returned.
+    :rtype: boolean
+
+    """
+    if urlparse(value).scheme in ('http', 'https', 'file'):
+        return False
+    return not os.path.isabs(value)
diff --git a/src/zeep/ns.py b/src/zeep/ns.py
index 66e1470..b78ebdf 100644
--- a/src/zeep/ns.py
+++ b/src/zeep/ns.py
@@ -4,6 +4,7 @@ SOAP_12 = 'http://schemas.xmlsoap.org/wsdl/soap12/'
 SOAP_ENV_11 = 'http://schemas.xmlsoap.org/soap/envelope/'
 SOAP_ENV_12 = 'http://www.w3.org/2003/05/soap-envelope'
 
+XSI = 'http://www.w3.org/2001/XMLSchema-instance'
 XSD = 'http://www.w3.org/2001/XMLSchema'
 
 WSDL = 'http://schemas.xmlsoap.org/wsdl/'
@@ -16,3 +17,7 @@ WSA = 'http://www.w3.org/2005/08/addressing'
 DS = 'http://www.w3.org/2000/09/xmldsig#'
 WSSE = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'
 WSU = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd'
+
+NAMESPACE_TO_PREFIX = {
+    XSD: 'xsd',
+}
diff --git a/src/zeep/parser.py b/src/zeep/parser.py
deleted file mode 100644
index 4d6082f..0000000
--- a/src/zeep/parser.py
+++ /dev/null
@@ -1,49 +0,0 @@
-import os
-
-from defusedxml.lxml import fromstring
-from lxml import etree
-
-from six.moves.urllib.parse import urljoin, urlparse
-from zeep.exceptions import XMLSyntaxError
-
-
-def parse_xml(content, base_url=None, recover=False):
-    parser = etree.XMLParser(
-        remove_comments=True, recover=recover, resolve_entities=False)
-    try:
-        return fromstring(content, parser=parser, base_url=base_url)
-    except etree.XMLSyntaxError as exc:
-        raise XMLSyntaxError("Invalid XML content received (%s)" % exc)
-
-
-def load_external(url, transport, base_url=None):
-    if base_url:
-        url = absolute_location(url, base_url)
-
-    response = transport.load(url)
-    return parse_xml(response, base_url)
-
-
-def absolute_location(location, base):
-    if location == base:
-        return location
-
-    if urlparse(location).scheme in ('http', 'https'):
-        return location
-
-    if base and urlparse(base).scheme in ('http', 'https'):
-        return urljoin(base, location)
-    else:
-        if os.path.isabs(location):
-            return location
-        if base:
-            return os.path.realpath(
-                os.path.join(os.path.dirname(base), location))
-    return location
-
-
-def is_relative_path(value):
-    """Check if the given value is a relative path"""
-    if urlparse(value).scheme in ('http', 'https', 'file'):
-        return False
-    return not os.path.isabs(value)
diff --git a/src/zeep/transports.py b/src/zeep/transports.py
index 89fd183..29a4453 100644
--- a/src/zeep/transports.py
+++ b/src/zeep/transports.py
@@ -3,9 +3,9 @@ import os
 from contextlib import contextmanager
 
 import requests
-
 from six.moves.urllib.parse import urlparse
-from zeep.utils import get_version
+
+from zeep.utils import get_media_type, get_version
 from zeep.wsdl.utils import etree_to_string
 
 
@@ -16,7 +16,7 @@ class Transport(object):
     :param timeout: The timeout for loading wsdl and xsd documents.
     :param operation_timeout: The timeout for operations (POST/GET). By
                               default this is None (no timeout).
-    :param session: A request.Session() object (optional)
+    :param session: A :py:class:`request.Session()` object (optional)
 
     """
     supports_async = False
@@ -68,7 +68,10 @@ class Transport(object):
             timeout=self.operation_timeout)
 
         if self.logger.isEnabledFor(logging.DEBUG):
-            if 'multipart/related' in response.headers.get('Content-Type'):
+            media_type = get_media_type(
+                response.headers.get('Content-Type', 'text/xml'))
+
+            if media_type == 'multipart/related':
                 log_message = response.content
             else:
                 log_message = response.content
diff --git a/src/zeep/utils.py b/src/zeep/utils.py
index d7efe90..2d15c51 100644
--- a/src/zeep/utils.py
+++ b/src/zeep/utils.py
@@ -1,7 +1,11 @@
+import cgi
 import inspect
 
 from lxml import etree
 
+from zeep.exceptions import XMLParseError
+from zeep.ns import XSD
+
 
 def qname_attr(node, attr_name, target_namespace=None):
     value = node.get(attr_name)
@@ -9,11 +13,25 @@ def qname_attr(node, attr_name, target_namespace=None):
         return as_qname(value, node.nsmap, target_namespace)
 
 
-def as_qname(value, nsmap, target_namespace):
+def as_qname(value, nsmap, target_namespace=None):
     """Convert the given value to a QName"""
     if ':' in value:
         prefix, local = value.split(':')
-        namespace = nsmap.get(prefix, prefix)
+
+        # The xml: prefix is always bound to the XML namespace, see
+        # https://www.w3.org/TR/xml-names/
+        if prefix == 'xml':
+            namespace = 'http://www.w3.org/XML/1998/namespace'
+        else:
+            namespace = nsmap.get(prefix)
+
+        if not namespace:
+            raise XMLParseError("No namespace defined for %r" % prefix)
+
+        # Workaround for https://github.com/mvantellingen/python-zeep/issues/349
+        if not local:
+            return etree.QName(XSD, 'anyType')
+
         return etree.QName(namespace, local)
 
     if target_namespace:
@@ -61,3 +79,9 @@ def get_base_class(objects):
 def detect_soap_env(envelope):
     root_tag = etree.QName(envelope)
     return root_tag.namespace
+
+
+def get_media_type(value):
+    """Parse a HTTP content-type header and return the media-type"""
+    main_value, parameters = cgi.parse_header(value)
+    return main_value
diff --git a/src/zeep/wsa.py b/src/zeep/wsa.py
index 3c3e653..a4ff68e 100644
--- a/src/zeep/wsa.py
+++ b/src/zeep/wsa.py
@@ -37,7 +37,5 @@ class WsAddressingPlugin(Plugin):
                 keep_ns_prefixes=header.nsmap,
                 top_nsmap=self.nsmap)
         else:
-            etree.cleanup_namespaces(
-                header,
-                keep_ns_prefixes=header.nsmap)
+            etree.cleanup_namespaces(header)
         return envelope, http_headers
diff --git a/src/zeep/wsdl/__init__.py b/src/zeep/wsdl/__init__.py
index 2dbac73..f24188e 100644
--- a/src/zeep/wsdl/__init__.py
+++ b/src/zeep/wsdl/__init__.py
@@ -1 +1,16 @@
+"""
+    zeep.wsdl
+    ---------
+
+    The wsdl module is responsible for parsing the WSDL document. This includes
+    the bindings and messages.
+
+    The structure and naming of the modules and classses closely follows the
+    WSDL 1.1 specification.
+
+    The serialization and deserialization of the SOAP/HTTP messages is done
+    by the zeep.wsdl.messages modules.
+
+
+"""
 from zeep.wsdl.wsdl import Document  # noqa
diff --git a/src/zeep/wsdl/attachments.py b/src/zeep/wsdl/attachments.py
index 408ae4d..505980c 100644
--- a/src/zeep/wsdl/attachments.py
+++ b/src/zeep/wsdl/attachments.py
@@ -3,8 +3,8 @@
 See https://www.w3.org/TR/SOAP-attachments
 
 """
+
 import base64
-from io import BytesIO
 
 from cached_property import cached_property
 from requests.structures import CaseInsensitiveDict
@@ -27,9 +27,21 @@ class MessagePack(object):
 
     @cached_property
     def attachments(self):
+        """Return a list of attachments.
+
+        :rtype: list of Attachment
+
+        """
         return [Attachment(part) for part in self._parts]
 
     def get_by_content_id(self, content_id):
+        """get_by_content_id
+
+        :param content_id: The content-id to return
+        :type content_id: str
+        :rtype: Attachment
+
+        """
         for attachment in self.attachments:
             if attachment.content_id == content_id:
                 return attachment
@@ -37,9 +49,9 @@ class MessagePack(object):
 
 class Attachment(object):
     def __init__(self, part):
-
+        encoding = part.encoding or 'utf-8'
         self.headers = CaseInsensitiveDict({
-            k.decode(part.encoding): v.decode(part.encoding)
+            k.decode(encoding): v.decode(encoding)
             for k, v in part.headers.items()
         })
         self.content_type = self.headers.get('Content-Type', None)
@@ -52,6 +64,11 @@ class Attachment(object):
 
     @cached_property
     def content(self):
+        """Return the content of the attachment
+
+        :rtype: bytes or str
+
+        """
         encoding = self.headers.get('Content-Transfer-Encoding', None)
         content = self._part.content
 
diff --git a/src/zeep/wsdl/bindings/soap.py b/src/zeep/wsdl/bindings/soap.py
index 6ba92e1..ae08ad7 100644
--- a/src/zeep/wsdl/bindings/soap.py
+++ b/src/zeep/wsdl/bindings/soap.py
@@ -5,8 +5,8 @@ from requests_toolbelt.multipart.decoder import MultipartDecoder
 
 from zeep import ns, plugins, wsa
 from zeep.exceptions import Fault, TransportError, XMLSyntaxError
-from zeep.parser import parse_xml
-from zeep.utils import as_qname, qname_attr
+from zeep.loader import parse_xml
+from zeep.utils import as_qname, get_media_type, qname_attr
 from zeep.wsdl.attachments import MessagePack
 from zeep.wsdl.definitions import Binding, Operation
 from zeep.wsdl.messages import DocumentMessage, RpcMessage
@@ -97,9 +97,9 @@ class SoapBinding(Binding):
         :type options: dict
         :param operation: The operation object from which this is a reply
         :type operation: zeep.wsdl.definitions.Operation
-        :param args: The *args to pass to the operation
+        :param args: The args to pass to the operation
         :type args: tuple
-        :param kwargs: The **kwargs to pass to the operation
+        :param kwargs: The kwargs to pass to the operation
         :type kwargs: dict
 
         """
@@ -112,6 +112,11 @@ class SoapBinding(Binding):
             options['address'], envelope, http_headers)
 
         operation_obj = self.get(operation)
+
+        # If the client wants to return the raw data then let's do that.
+        if client.raw_response:
+            return response
+
         return self.process_reply(client, operation_obj, response)
 
     def process_reply(self, client, operation, response):
@@ -131,20 +136,27 @@ class SoapBinding(Binding):
                 % response.status_code)
 
         content_type = response.headers.get('Content-Type', 'text/xml')
-        if 'multipart/related' in content_type:
-            decoder = MultipartDecoder(response.content, content_type, 'utf-8')
+        media_type = get_media_type(content_type)
+        message_pack = None
+
+        if media_type == 'multipart/related':
+            decoder = MultipartDecoder(
+                response.content, content_type, response.encoding or 'utf-8')
+
             content = decoder.parts[0].content
             if len(decoder.parts) > 1:
                 message_pack = MessagePack(parts=decoder.parts[1:])
         else:
             content = response.content
-            message_pack = None
 
         try:
-            doc = parse_xml(content)
+            doc = parse_xml(
+                content, self.transport,
+                strict=client.wsdl.strict,
+                xml_huge_tree=client.xml_huge_tree)
         except XMLSyntaxError:
             raise TransportError(
-                u'Server returned HTTP status %d (%s)'
+                'Server returned HTTP status %d (%s)'
                 % (response.status_code, response.content))
 
         if client.wsse:
@@ -187,6 +199,9 @@ class SoapBinding(Binding):
     @classmethod
     def parse(cls, definitions, xmlelement):
         """
+
+        Definition::
+
             <wsdl:binding name="nmtoken" type="qname"> *
                 <-- extensibility element (1) --> *
                 <wsdl:operation name="nmtoken"> *
@@ -210,7 +225,13 @@ class SoapBinding(Binding):
         # default style attribute for the operations.
         soap_node = xmlelement.find('soap:binding', namespaces=cls.nsmap)
         transport = soap_node.get('transport')
-        if transport != 'http://schemas.xmlsoap.org/soap/http':
+
+        supported_transports = [
+            'http://schemas.xmlsoap.org/soap/http',
+            'http://www.w3.org/2003/05/soap/bindings/HTTP/',
+        ]
+
+        if transport not in supported_transports:
             raise NotImplementedError(
                 "The binding transport %s is not supported (only soap/http)" % (
                     transport))
@@ -328,12 +349,15 @@ class SoapOperation(Operation):
                 "{%s}Envelope root element. The root element found is %s "
             ) % (envelope_qname.namespace, envelope.tag))
 
-        return self.output.deserialize(envelope)
+        if self.output:
+            return self.output.deserialize(envelope)
 
     @classmethod
     def parse(cls, definitions, xmlelement, binding, nsmap):
         """
 
+        Definition::
+
             <wsdl:operation name="nmtoken"> *
                 <soap:operation soapAction="uri"? style="rpc|document"?>?
                 <wsdl:input name="nmtoken"? > ?
diff --git a/src/zeep/wsdl/definitions.py b/src/zeep/wsdl/definitions.py
index b01e1c2..41194b8 100644
--- a/src/zeep/wsdl/definitions.py
+++ b/src/zeep/wsdl/definitions.py
@@ -1,7 +1,27 @@
+"""
+    zeep.wsdl.definitions
+    ~~~~~~~~~~~~~~~~~~~~~
+
+    A WSDL document exists out of a number of definitions. There are 6 major
+    definitions, these are:
+
+     - types
+     - message
+     - portType
+     - binding
+     - port
+     - service
+
+    This module defines the definitions which occur within a WSDL document,
+
+"""
+import warnings
 from collections import OrderedDict, namedtuple
 
 from six import python_2_unicode_compatible
 
+from zeep.exceptions import IncompleteOperation
+
 MessagePart = namedtuple('MessagePart', ['element', 'type'])
 
 
@@ -13,8 +33,8 @@ class AbstractMessage(object):
     extensible. WSDL defines several such message-typing attributes for use
     with XSD:
 
-        element: Refers to an XSD element using a QName.
-        type: Refers to an XSD simpleType or complexType using a QName.
+        - element: Refers to an XSD element using a QName.
+        - type: Refers to an XSD simpleType or complexType using a QName.
 
     """
     def __init__(self, name):
@@ -72,6 +92,8 @@ class PortType(object):
 class Binding(object):
     """Base class for the various bindings (SoapBinding / HttpBinding)
 
+    .. raw:: ascii
+
         Binding
            |
            +-> Operation
@@ -100,8 +122,13 @@ class Binding(object):
 
     def resolve(self, definitions):
         self.port_type = definitions.get('port_types', self.port_name.text)
-        for operation in self._operations.values():
-            operation.resolve(definitions)
+
+        for name, operation in list(self._operations.items()):
+            try:
+                operation.resolve(definitions)
+            except IncompleteOperation as exc:
+                warnings.warn(str(exc))
+                del self._operations[name]
 
     def _operation_add(self, operation):
         # XXX: operation name is not unique
@@ -146,7 +173,12 @@ class Operation(object):
         self.faults = {}
 
     def resolve(self, definitions):
-        self.abstract = self.binding.port_type.operations[self.name]
+        try:
+            self.abstract = self.binding.port_type.operations[self.name]
+        except KeyError:
+            raise IncompleteOperation(
+                "The wsdl:operation %r was not found in the wsdl:portType %r" % (
+                    self.name, self.binding.port_type.name.text))
 
     def __repr__(self):
         return '<%s(name=%r, style=%r)>' % (
@@ -170,6 +202,9 @@ class Operation(object):
     @classmethod
     def parse(cls, wsdl, xmlelement, binding):
         """
+
+        Definition::
+
             <wsdl:operation name="nmtoken"> *
                <-- extensibility element (2) --> *
                <wsdl:input name="nmtoken"? > ?
@@ -182,12 +217,17 @@ class Operation(object):
                    <-- extensibility element (5) --> *
                </wsdl:fault>
             </wsdl:operation>
+
         """
         raise NotImplementedError()
 
 
 @python_2_unicode_compatible
 class Port(object):
+    """Specifies an address for a binding, thus defining a single communication
+    endpoint.
+
+    """
     def __init__(self, name, binding_name, xmlelement):
         self.name = name
         self._resolve_context = {
@@ -231,7 +271,9 @@ class Port(object):
 
 @python_2_unicode_compatible
 class Service(object):
+    """Used to aggregate a set of related ports.
 
+    """
     def __init__(self, name):
         self.ports = OrderedDict()
         self.name = name
diff --git a/src/zeep/wsdl/messages/__init__.py b/src/zeep/wsdl/messages/__init__.py
index 70a5b5a..f77f710 100644
--- a/src/zeep/wsdl/messages/__init__.py
+++ b/src/zeep/wsdl/messages/__init__.py
@@ -1,3 +1,20 @@
+"""
+    zeep.wsdl.messages
+    ~~~~~~~~~~~~~~~~~~
+
+    The messages are responsible for serializing and deserializing
+
+    .. inheritance-diagram::
+            zeep.wsdl.messages.soap.DocumentMessage
+            zeep.wsdl.messages.soap.RpcMessage
+            zeep.wsdl.messages.http.UrlEncoded
+            zeep.wsdl.messages.http.UrlReplacement
+            zeep.wsdl.messages.mime.MimeContent
+            zeep.wsdl.messages.mime.MimeXML
+            zeep.wsdl.messages.mime.MimeMultipart
+       :parts: 1
+
+"""
 from .http import *  # noqa
 from .mime import *  # noqa
 from .soap import *  # noqa
diff --git a/src/zeep/wsdl/messages/base.py b/src/zeep/wsdl/messages/base.py
index cc71e93..eebb840 100644
--- a/src/zeep/wsdl/messages/base.py
+++ b/src/zeep/wsdl/messages/base.py
@@ -1,3 +1,8 @@
+"""
+    zeep.wsdl.messages.base
+    ~~~~~~~~~~~~~~~~~~~~~~~
+
+"""
 from collections import namedtuple
 
 from zeep import xsd
@@ -31,15 +36,17 @@ class ConcreteMessage(object):
             if isinstance(self.body.type, xsd.ComplexType):
                 try:
                     if len(self.body.type.elements) == 1:
-                        return self.body.type.elements[0][1].type.signature()
+                        return self.body.type.elements[0][1].type.signature(
+                            schema=self.wsdl.types, standalone=False)
                 except AttributeError:
                     return None
 
-            return self.body.type.signature()
+            return self.body.type.signature(schema=self.wsdl.types, standalone=False)
 
-        parts = [self.body.type.signature()]
+        parts = [self.body.type.signature(schema=self.wsdl.types, standalone=False)]
         if getattr(self, 'header', None):
-            parts.append('_soapheaders={%s}' % self.header.signature())
+            parts.append('_soapheaders={%s}' % self.header.signature(
+                schema=self.wsdl.types), standalone=False)
         return ', '.join(part for part in parts if part)
 
     @classmethod
diff --git a/src/zeep/wsdl/messages/http.py b/src/zeep/wsdl/messages/http.py
index 6264dca..30ac121 100644
--- a/src/zeep/wsdl/messages/http.py
+++ b/src/zeep/wsdl/messages/http.py
@@ -1,3 +1,8 @@
+"""
+    zeep.wsdl.messages.http
+    ~~~~~~~~~~~~~~~~~~~~~~~
+
+"""
 from zeep import xsd
 from zeep.wsdl.messages.base import ConcreteMessage, SerializedMessage
 
diff --git a/src/zeep/wsdl/messages/mime.py b/src/zeep/wsdl/messages/mime.py
index d509212..acc00a7 100644
--- a/src/zeep/wsdl/messages/mime.py
+++ b/src/zeep/wsdl/messages/mime.py
@@ -1,3 +1,8 @@
+"""
+    zeep.wsdl.messages.mime
+    ~~~~~~~~~~~~~~~~~~~~~~~
+
+"""
 import six
 from defusedxml.lxml import fromstring
 from lxml import etree
@@ -79,6 +84,14 @@ class MimeContent(MimeMessage):
     The set of defined MIME types is both large and evolving, so it is not a
     goal for WSDL to exhaustively define XML grammar for each MIME type.
 
+    :param wsdl: The main wsdl document
+    :type wsdl: zeep.wsdl.wsdl.Document
+    :param name:
+    :param operation: The operation to which this message belongs
+    :type operation: zeep.wsdl.bindings.soap.SoapOperation
+    :param part_name:
+    :type type: str
+
     """
     def __init__(self, wsdl, name, operation, content_type, part_name):
         super(MimeContent, self).__init__(wsdl, name, operation, part_name)
@@ -131,6 +144,14 @@ class MimeXML(MimeMessage):
     only a single part. The part references a concrete schema using the element
     attribute for simple parts or type attribute for composite parts
 
+    :param wsdl: The main wsdl document
+    :type wsdl: zeep.wsdl.wsdl.Document
+    :param name:
+    :param operation: The operation to which this message belongs
+    :type operation: zeep.wsdl.bindings.soap.SoapOperation
+    :param part_name:
+    :type type: str
+
     """
     def serialize(self, *args, **kwargs):
         raise NotImplementedError()
@@ -170,5 +191,13 @@ class MimeMultipart(MimeMessage):
     the part. If more than one MIME element appears inside a mime:part, they
     are alternatives.
 
+    :param wsdl: The main wsdl document
+    :type wsdl: zeep.wsdl.wsdl.Document
+    :param name:
+    :param operation: The operation to which this message belongs
+    :type operation: zeep.wsdl.bindings.soap.SoapOperation
+    :param part_name:
+    :type type: str
+
     """
     pass
diff --git a/src/zeep/wsdl/messages/multiref.py b/src/zeep/wsdl/messages/multiref.py
new file mode 100644
index 0000000..907049e
--- /dev/null
+++ b/src/zeep/wsdl/messages/multiref.py
@@ -0,0 +1,81 @@
+import copy
+
+from lxml import etree
+
+
+def process_multiref(node):
+    """Iterate through the tree and replace the referened elements.
+
+    This method replaces the nodes with an href attribute and replaces it
+    with the elements it's referencing to (which have an id attribute).abs
+
+    """
+    multiref_objects = {
+        elm.attrib['id']: elm for elm in node.xpath('*[@id]')
+    }
+    if not multiref_objects:
+        return
+
+    used_nodes = []
+
+    def process(node):
+        # TODO (In Soap 1.2 this is 'ref')
+        href = node.attrib.get('href')
+
+        if href and href.startswith('#'):
+            obj = multiref_objects.get(href[1:])
+            if obj is not None:
+                used_nodes.append(obj)
+                parent = node.getparent()
+
+                new = _dereference_element(obj, node)
+
+                # Replace the node with the new dereferenced node
+                parent.insert(parent.index(node), new)
+                parent.remove(node)
+                node = new
+
+        for child in node:
+            process(child)
+
+    process(node)
+
+    # Remove the old dereferenced nodes from the tree
+    for node in used_nodes:
+        parent = node.getparent()
+        if parent is not None:
+            parent.remove(node)
+
+
+def _dereference_element(source, target):
+    reverse_nsmap = {v: k for k, v in target.nsmap.items()}
+    specific_nsmap = {k: v for k, v in source.nsmap.items() if k not in target.nsmap}
+
+    new = etree.Element(target.tag, nsmap=specific_nsmap)
+
+    # Copy the attributes. This is actually the difficult part since the
+    # namespace prefixes can change in the attribute values. So for example
+    # the xsi:type="ns11:my-type" need's to be parsed to use a new global
+    # prefix.
+    for key, value in source.attrib.items():
+        if key == 'id':
+            continue
+
+        setted = False
+        if value.count(':') == 1:
+            prefix, localname = value.split(':')
+            if prefix in specific_nsmap:
+                namespace = specific_nsmap[prefix]
+                if namespace in reverse_nsmap:
+                    new.set(key, '%s:%s' % (reverse_nsmap[namespace], localname))
+                    setted = True
+
+        if not setted:
+            new.set(key, value)
+
+    # Copy the children and the text content
+    for child in source:
+        new.append(copy.deepcopy(child))
+    new.text = source.text
+
+    return new
diff --git a/src/zeep/wsdl/messages/soap.py b/src/zeep/wsdl/messages/soap.py
index 17a2d2d..940a2e6 100644
--- a/src/zeep/wsdl/messages/soap.py
+++ b/src/zeep/wsdl/messages/soap.py
@@ -1,12 +1,19 @@
+"""
+    zeep.wsdl.messages.soap
+    ~~~~~~~~~~~~~~~~~~~~~~~
+
+"""
 import copy
 from collections import OrderedDict
 
 from lxml import etree
 from lxml.builder import ElementMaker
 
+from zeep import ns
 from zeep import exceptions, xsd
 from zeep.utils import as_qname
 from zeep.wsdl.messages.base import ConcreteMessage, SerializedMessage
+from zeep.wsdl.messages.multiref import process_multiref
 
 __all__ = [
     'DocumentMessage',
@@ -15,8 +22,19 @@ __all__ = [
 
 
 class SoapMessage(ConcreteMessage):
-    """Base class for the SOAP Document and RPC messages"""
+    """Base class for the SOAP Document and RPC messages
+
+    :param wsdl: The main wsdl document
+    :type wsdl: zeep.wsdl.Document
+    :param name:
+    :param operation: The operation to which this message belongs
+    :type operation: zeep.wsdl.bindings.soap.SoapOperation
+    :param type: 'input' or 'output'
+    :type type: str
+    :param nsmap: The namespace mapping
+    :type nsmap: dict
 
+    """
     def __init__(self, wsdl, name, operation, type, nsmap):
         super(SoapMessage, self).__init__(wsdl, name, operation)
         self.nsmap = nsmap
@@ -71,6 +89,7 @@ class SoapMessage(ConcreteMessage):
         if not self.envelope:
             return None
 
+
         body = envelope.find('soap-env:Body', namespaces=self.nsmap)
         body_result = self._deserialize_body(body)
 
@@ -96,7 +115,8 @@ class SoapMessage(ConcreteMessage):
         result = next(iter(result.__values__.values()))
         if isinstance(result, xsd.CompoundValue):
             children = result._xsd_type.elements
-            if len(children) == 1:
+            attributes = result._xsd_type.attributes
+            if len(children) == 1 and len(attributes) == 0:
                 item_name, item_element = children[0]
                 retval = getattr(result, item_name)
                 return retval
@@ -110,14 +130,16 @@ class SoapMessage(ConcreteMessage):
             if isinstance(self.envelope.type, xsd.ComplexType):
                 try:
                     if len(self.envelope.type.elements) == 1:
-                        return self.envelope.type.elements[0][1].type.signature()
+                        return self.envelope.type.elements[0][1].type.signature(
+                            schema=self.wsdl.types, standalone=False)
                 except AttributeError:
                     return None
-            return self.envelope.type.signature()
+            return self.envelope.type.signature(schema=self.wsdl.types, standalone=False)
 
-        parts = [self.body.type.signature()]
+        parts = [self.body.type.signature(schema=self.wsdl.types, standalone=False)]
         if self.header.type._element:
-            parts.append('_soapheaders={%s}' % self.header.signature())
+            parts.append('_soapheaders={%s}' % self.header.type.signature(
+                schema=self.wsdl.types, standalone=False))
         return ', '.join(part for part in parts if part)
 
     @classmethod
@@ -156,6 +178,8 @@ class SoapMessage(ConcreteMessage):
         body_data = None
         header_data = None
 
+        # After some profiling it turns out that .find() and .findall() in this
+        # case are twice as fast as the xpath method
         body = xmlelement.find('soap:body', namespaces=operation.binding.nsmap)
         if body is not None:
             body_data = cls._parse_body(body)
@@ -270,15 +294,16 @@ class SoapMessage(ConcreteMessage):
         elements from the body and the headers.
 
         """
-        all_elements = xsd.Sequence([
-            xsd.Element('body', self.body.type),
-        ])
+        all_elements = xsd.Sequence([])
 
         if self.header.type._element:
             all_elements.append(
-                xsd.Element('header', self.header.type))
+                xsd.Element('{%s}header' % self.nsmap['soap-env'], self.header.type))
 
-        return xsd.Element('envelope', xsd.ComplexType(all_elements))
+        all_elements.append(
+            xsd.Element('{%s}body' % self.nsmap['soap-env'], self.body.type))
+
+        return xsd.Element('{%s}envelope' % self.nsmap['soap-env'], xsd.ComplexType(all_elements))
 
     def _serialize_header(self, headers_value, nsmap):
         if not headers_value:
@@ -327,9 +352,9 @@ class SoapMessage(ConcreteMessage):
     def _resolve_header(self, info, definitions, parts):
         name = etree.QName(self.nsmap['soap-env'], 'Header')
 
-        sequence = xsd.Sequence()
+        container = xsd.All(consume_other=True)
         if not info:
-            return xsd.Element(name, xsd.ComplexType(sequence))
+            return xsd.Element(name, xsd.ComplexType(container))
 
         for item in info:
             message_name = item['message'].text
@@ -345,14 +370,28 @@ class SoapMessage(ConcreteMessage):
                 element.attr_name = part_name
             else:
                 element = xsd.Element(part_name, part.type)
-            sequence.append(element)
-        return xsd.Element(name, xsd.ComplexType(sequence))
+            container.append(element)
+        return xsd.Element(name, xsd.ComplexType(container))
 
 
 class DocumentMessage(SoapMessage):
     """In the document message there are no additional wrappers, and the
     message parts appear directly under the SOAP Body element.
 
+    .. inheritance-diagram:: zeep.wsdl.messages.soap.DocumentMessage
+       :parts: 1
+
+    :param wsdl: The main wsdl document
+    :type wsdl: zeep.wsdl.Document
+    :param name:
+    :param operation: The operation to which this message belongs
+    :type operation: zeep.wsdl.bindings.soap.SoapOperation
+    :param type: 'input' or 'output'
+    :type type: str
+    :param nsmap: The namespace mapping
+    :type nsmap: dict
+
+
     """
 
     def __init__(self, *args, **kwargs):
@@ -407,6 +446,20 @@ class RpcMessage(SoapMessage):
     identically to the corresponding parameter of the call.  Parts are arranged
     in the same order as the parameters of the call.
 
+    .. inheritance-diagram:: zeep.wsdl.messages.soap.DocumentMessage
+       :parts: 1
+
+
+    :param wsdl: The main wsdl document
+    :type wsdl: zeep.wsdl.Document
+    :param name:
+    :param operation: The operation to which this message belongs
+    :type operation: zeep.wsdl.bindings.soap.SoapOperation
+    :param type: 'input' or 'output'
+    :type type: str
+    :param nsmap: The namespace mapping
+    :type nsmap: dict
+
     """
 
     def _resolve_body(self, info, definitions, parts):
@@ -444,6 +497,8 @@ class RpcMessage(SoapMessage):
         element.
 
         """
+        process_multiref(body_element)
+
         response_element = body_element.getchildren()[0]
         if self.body:
             result = self.body.parse(response_element, self.wsdl.types)
diff --git a/src/zeep/wsdl/parse.py b/src/zeep/wsdl/parse.py
index 865ce15..f2b1506 100644
--- a/src/zeep/wsdl/parse.py
+++ b/src/zeep/wsdl/parse.py
@@ -1,5 +1,11 @@
+"""
+    zeep.wsdl.parse
+    ~~~~~~~~~~~~~~~
+
+"""
 from lxml import etree
 
+from zeep.exceptions import IncompleteMessage, LookupError, NamespaceError
 from zeep.utils import qname_attr
 from zeep.wsdl import definitions
 
@@ -12,11 +18,20 @@ NSMAP = {
 def parse_abstract_message(wsdl, xmlelement):
     """Create an AbstractMessage object from a xml element.
 
+    Definition::
+
         <definitions .... >
             <message name="nmtoken"> *
                 <part name="nmtoken" element="qname"? type="qname"?/> *
             </message>
         </definitions>
+
+    :param wsdl: The parent definition instance
+    :type wsdl: zeep.wsdl.wsdl.Definition
+    :param xmlelement: The XML node
+    :type xmlelement: lxml.etree._Element
+    :rtype: zeep.wsdl.definitions.AbstractMessage
+
     """
     tns = wsdl.target_namespace
     parts = []
@@ -26,10 +41,17 @@ def parse_abstract_message(wsdl, xmlelement):
         part_element = qname_attr(part, 'element', tns)
         part_type = qname_attr(part, 'type', tns)
 
-        if part_element is not None:
-            part_element = wsdl.types.get_element(part_element)
-        if part_type is not None:
-            part_type = wsdl.types.get_type(part_type)
+        try:
+            if part_element is not None:
+                part_element = wsdl.types.get_element(part_element)
+            if part_type is not None:
+                part_type = wsdl.types.get_type(part_type)
+
+        except (NamespaceError, LookupError):
+            raise IncompleteMessage((
+                "The wsdl:message for %r contains "
+                "invalid xsd types or elements"
+            ) % part_name)
 
         part = definitions.MessagePart(part_element, part_type)
         parts.append((part_name, part))
@@ -48,6 +70,8 @@ def parse_abstract_operation(wsdl, xmlelement):
     This is called from the parse_port_type function since the abstract
     operations are part of the port type element.
 
+    Definition::
+
         <wsdl:operation name="nmtoken">*
            <wsdl:documentation .... /> ?
            <wsdl:input name="nmtoken"? message="qname">?
@@ -61,6 +85,12 @@ def parse_abstract_operation(wsdl, xmlelement):
            </wsdl:fault>
         </wsdl:operation>
 
+    :param wsdl: The parent definition instance
+    :type wsdl: zeep.wsdl.wsdl.Definition
+    :param xmlelement: The XML node
+    :type xmlelement: lxml.etree._Element
+    :rtype: zeep.wsdl.definitions.AbstractOperation
+
     """
     name = xmlelement.get('name')
     kwargs = {
@@ -75,7 +105,11 @@ def parse_abstract_operation(wsdl, xmlelement):
         param_msg = qname_attr(
             msg_node, 'message', wsdl.target_namespace)
         param_name = msg_node.get('name')
-        param_value = wsdl.get('messages', param_msg.text)
+
+        try:
+            param_value = wsdl.get('messages', param_msg.text)
+        except IndexError:
+            return
 
         if tag_name == 'input':
             kwargs['input_message'] = param_value
@@ -95,18 +129,27 @@ def parse_abstract_operation(wsdl, xmlelement):
 def parse_port_type(wsdl, xmlelement):
     """Create a PortType object from a xml element.
 
+    Definition::
+
         <wsdl:definitions .... >
             <wsdl:portType name="nmtoken">
                 <wsdl:operation name="nmtoken" .... /> *
             </wsdl:portType>
         </wsdl:definitions>
 
+    :param wsdl: The parent definition instance
+    :type wsdl: zeep.wsdl.wsdl.Definition
+    :param xmlelement: The XML node
+    :type xmlelement: lxml.etree._Element
+    :rtype: zeep.wsdl.definitions.PortType
+
     """
     name = qname_attr(xmlelement, 'name', wsdl.target_namespace)
     operations = {}
     for elm in xmlelement.findall('wsdl:operation', namespaces=NSMAP):
         operation = parse_abstract_operation(wsdl, elm)
-        operations[operation.name] = operation
+        if operation:
+            operations[operation.name] = operation
     return definitions.PortType(name, operations)
 
 
@@ -116,11 +159,19 @@ def parse_port(wsdl, xmlelement):
     This is called via the parse_service function since ports are part of the
     service xml elements.
 
+    Definition::
+
         <wsdl:port name="nmtoken" binding="qname"> *
            <wsdl:documentation .... /> ?
            <-- extensibility element -->
         </wsdl:port>
 
+    :param wsdl: The parent definition instance
+    :type wsdl: zeep.wsdl.wsdl.Definition
+    :param xmlelement: The XML node
+    :type xmlelement: lxml.etree._Element
+    :rtype: zeep.wsdl.definitions.Port
+
     """
     name = xmlelement.get('name')
     binding_name = qname_attr(xmlelement, 'binding', wsdl.target_namespace)
@@ -130,7 +181,7 @@ def parse_port(wsdl, xmlelement):
 def parse_service(wsdl, xmlelement):
     """
 
-    Syntax::
+    Definition::
 
         <wsdl:service name="nmtoken"> *
             <wsdl:documentation .... />?
@@ -150,6 +201,12 @@ def parse_service(wsdl, xmlelement):
             </port>
           </service>
 
+    :param wsdl: The parent definition instance
+    :type wsdl: zeep.wsdl.wsdl.Definition
+    :param xmlelement: The XML node
+    :type xmlelement: lxml.etree._Element
+    :rtype: zeep.wsdl.definitions.Service
+
     """
     name = xmlelement.get('name')
     ports = []
diff --git a/src/zeep/wsdl/utils.py b/src/zeep/wsdl/utils.py
index 6785d56..e73ac20 100644
--- a/src/zeep/wsdl/utils.py
+++ b/src/zeep/wsdl/utils.py
@@ -1,3 +1,8 @@
+"""
+    zeep.wsdl.utils
+    ~~~~~~~~~~~~~~~
+
+"""
 from lxml import etree
 from six.moves.urllib.parse import urlparse, urlunparse
 
diff --git a/src/zeep/wsdl/wsdl.py b/src/zeep/wsdl/wsdl.py
index 23a81cb..bc43806 100644
--- a/src/zeep/wsdl/wsdl.py
+++ b/src/zeep/wsdl/wsdl.py
@@ -1,15 +1,21 @@
+"""
+    zeep.wsdl.wsdl
+    ~~~~~~~~~~~~~~
+
+"""
 from __future__ import print_function
 
 import logging
 import operator
 import os
+import warnings
 from collections import OrderedDict
 
 import six
 from lxml import etree
 
-from zeep.parser import (
-    absolute_location, is_relative_path, load_external, parse_xml)
+from zeep.exceptions import IncompleteMessage
+from zeep.loader import absolute_location, is_relative_path, load_external
 from zeep.utils import findall_multiple_ns
 from zeep.wsdl import parse
 from zeep.xsd import Schema
@@ -34,18 +40,23 @@ class Document(object):
     resolves references which were not yet available during the initial
     parsing phase.
 
+
+    :param location: Location of this WSDL
+    :type location: string
+    :param transport: The transport object to be used
+    :type transport: zeep.transports.Transport
+    :param base: The base location of this document
+    :type base: str
+    :param strict: Indicates if strict mode is enabled
+    :type strict: bool
+
     """
 
-    def __init__(self, location, transport, base=None):
+    def __init__(self, location, transport, base=None, strict=True):
         """Initialize a WSDL document.
 
         The root definition properties are exposed as entry points.
 
-        :param location: Location of this WSDL
-        :type location: string
-        :param transport: The transport object to be used
-        :type transport: zeep.transports.Transport
-
         """
         if isinstance(location, six.string_types):
             if is_relative_path(location):
@@ -55,12 +66,17 @@ class Document(object):
             self.location = base
 
         self.transport = transport
+        self.strict = strict
 
         # Dict with all definition objects within this WSDL
         self._definitions = {}
-        self.types = Schema([], transport=self.transport, location=self.location)
+        self.types = Schema(
+            node=None,
+            transport=self.transport,
+            location=self.location,
+            strict=self.strict)
 
-        document = self._load_content(location)
+        document = self._get_xml_document(location)
 
         root_definitions = Definition(self, document, self.location)
         root_definitions.resolve_imports()
@@ -75,8 +91,6 @@ class Document(object):
         return '<WSDL(location=%r)>' % self.location
 
     def dump(self):
-        namespaces = {v: k for k, v in self.types.prefix_map.items()}
-
         print('')
         print("Prefixes:")
         for prefix, namespace in self.types.prefix_map.items():
@@ -84,18 +98,14 @@ class Document(object):
 
         print('')
         print("Global elements:")
-        for elm_obj in sorted(self.types.elements, key=lambda k: six.text_type(k)):
-            value = six.text_type(elm_obj)
-            if hasattr(elm_obj, 'qname') and elm_obj.qname.namespace:
-                value = '%s:%s' % (namespaces[elm_obj.qname.namespace], value)
+        for elm_obj in sorted(self.types.elements, key=lambda k: k.qname):
+            value = elm_obj.signature(schema=self.types)
             print(' ' * 4, value)
 
         print('')
         print("Global types:")
         for type_obj in sorted(self.types.types, key=lambda k: k.qname or ''):
-            value = six.text_type(type_obj)
-            if getattr(type_obj, 'qname', None) and type_obj.qname.namespace:
-                value = '%s:%s' % (namespaces[type_obj.qname.namespace], value)
+            value = type_obj.signature(schema=self.types)
             print(' ' * 4, value)
 
         print('')
@@ -118,7 +128,7 @@ class Document(object):
                     print('%s%s' % (' ' * 12, six.text_type(operation)))
                 print('')
 
-    def _load_content(self, location):
+    def _get_xml_document(self, location):
         """Load the XML content from the given location and return an
         lxml.Element object.
 
@@ -126,9 +136,8 @@ class Document(object):
         :type location: string
 
         """
-        if hasattr(location, 'read'):
-            return parse_xml(location.read())
-        return load_external(location, self.transport, self.location)
+        return load_external(
+            location, self.transport, self.location, strict=self.strict)
 
     def _add_definition(self, definition):
         key = (definition.target_namespace, definition.location)
@@ -136,9 +145,18 @@ class Document(object):
 
 
 class Definition(object):
-    """The Definition represents one wsdl:definition within a Document."""
+    """The Definition represents one wsdl:definition within a Document.
+
+    :param wsdl: The wsdl
+
+    """
 
     def __init__(self, wsdl, doc, location):
+        """fo
+
+        :param wsdl: The wsdl
+
+        """
         logger.debug("Creating definition for %s", location)
         self.wsdl = wsdl
         self.location = location
@@ -183,7 +201,16 @@ class Definition(object):
                 try:
                     return definition.get(name, key, _processed)
                 except IndexError:
-                    pass
+                    # Try to see if there is an item which has no namespace
+                    # but where the localname matches. This is basically for
+                    # #356 but in the future we should also ignore mismatching
+                    # namespaces as last fallback
+                    fallback_key = etree.QName(key).localname
+                    try:
+                        return definition.get(name, fallback_key, _processed)
+                    except IndexError:
+                        pass
+
         raise IndexError("No definition %r in %r found" % (key, name))
 
     def resolve_imports(self):
@@ -234,7 +261,7 @@ class Definition(object):
             if key in self.wsdl._definitions:
                 self.imports[key] = self.wsdl._definitions[key]
             else:
-                document = self.wsdl._load_content(location)
+                document = self.wsdl._get_xml_document(location)
                 if etree.QName(document.tag).localname == 'schema':
                     self.types.add_documents([document], location)
                 else:
@@ -251,6 +278,8 @@ class Definition(object):
         If the wsdl:types doesn't container an xml schema then an empty schema
         is returned instead.
 
+        Definition::
+
             <definitions .... >
                 <types>
                     <xsd:schema .... />*
@@ -279,6 +308,9 @@ class Definition(object):
 
     def parse_messages(self, doc):
         """
+
+        Definition::
+
             <definitions .... >
                 <message name="nmtoken"> *
                     <part name="nmtoken" element="qname"? type="qname"?/> *
@@ -291,14 +323,20 @@ class Definition(object):
         """
         result = {}
         for msg_node in doc.findall("wsdl:message", namespaces=NSMAP):
-            msg = parse.parse_abstract_message(self, msg_node)
-            result[msg.name.text] = msg
-            logger.debug("Adding message: %s", msg.name.text)
+            try:
+                msg = parse.parse_abstract_message(self, msg_node)
+            except IncompleteMessage as exc:
+                warnings.warn(str(exc))
+            else:
+                result[msg.name.text] = msg
+                logger.debug("Adding message: %s", msg.name.text)
         return result
 
     def parse_ports(self, doc):
         """Return dict with `PortType` instances as values
 
+        Definition::
+
             <wsdl:definitions .... >
                 <wsdl:portType name="nmtoken">
                     <wsdl:operation name="nmtoken" .... /> *
@@ -323,7 +361,7 @@ class Definition(object):
         HTTP Post. The detection of the type of bindings is done by the
         bindings themselves using the introspection of the xml nodes.
 
-        XML Structure::
+        Definition::
 
             <wsdl:definitions .... >
                 <wsdl:binding name="nmtoken" type="qname"> *
@@ -345,6 +383,9 @@ class Definition(object):
 
         :param doc: The source document
         :type doc: lxml.etree._Element
+        :returns: Dictionary with binding name as key and Binding instance as
+          value
+        :rtype: dict
 
         """
         result = {}
@@ -382,6 +423,9 @@ class Definition(object):
 
     def parse_service(self, doc):
         """
+
+        Definition::
+
             <wsdl:definitions .... >
                 <wsdl:service .... > *
                     <wsdl:port name="nmtoken" binding="qname"> *
diff --git a/src/zeep/wsse/signature.py b/src/zeep/wsse/signature.py
index 120619a..ccc8e18 100644
--- a/src/zeep/wsse/signature.py
+++ b/src/zeep/wsse/signature.py
@@ -11,38 +11,63 @@ module.
 from lxml import etree
 from lxml.etree import QName
 
+from zeep import ns
+from zeep.exceptions import SignatureVerificationFailed
+from zeep.utils import detect_soap_env
+from zeep.wsse.utils import ensure_id, get_security_header
+
 try:
     import xmlsec
 except ImportError:
     xmlsec = None
 
-from zeep import ns
-from zeep.utils import detect_soap_env
-from zeep.exceptions import SignatureVerificationFailed
-from zeep.wsse.utils import ensure_id, get_security_header
 
 # SOAP envelope
 SOAP_NS = 'http://schemas.xmlsoap.org/soap/envelope/'
 
+def _read_file(f_name):
+    with open(f_name, "rb") as f:
+        return f.read()
+
+def _make_sign_key(key_data, cert_data, password):
+    key = xmlsec.Key.from_memory(key_data,
+                                 xmlsec.KeyFormat.PEM, password)
+    key.load_cert_from_memory(cert_data,
+                              xmlsec.KeyFormat.PEM)
+    return key
+
+def _make_verify_key(cert_data):
+    key = xmlsec.Key.from_memory(cert_data,
+                                 xmlsec.KeyFormat.CERT_PEM, None)
+    return key
 
-class Signature(object):
+class MemorySignature(object):
     """Sign given SOAP envelope with WSSE sig using given key and cert."""
 
-    def __init__(self, key_file, certfile, password=None):
+    def __init__(self, key_data, cert_data, password=None):
         check_xmlsec_import()
 
-        self.key_file = key_file
-        self.certfile = certfile
+        self.key_data = key_data
+        self.cert_data = cert_data
         self.password = password
 
     def apply(self, envelope, headers):
-        sign_envelope(envelope, self.key_file, self.certfile, self.password)
+        key = _make_sign_key(self.key_data, self.cert_data, self.password)
+        _sign_envelope_with_key(envelope, key)
         return envelope, headers
 
     def verify(self, envelope):
-        verify_envelope(envelope, self.certfile)
+        key = _make_verify_key(self.cert_data)
+        _verify_envelope_with_key(envelope, key)
         return envelope
 
+class Signature(MemorySignature):
+    """Sign given SOAP envelope with WSSE sig using given key file and cert file."""
+
+    def __init__(self, key_file, certfile, password=None):
+        super(Signature, self).__init__(_read_file(key_file),
+                                        _read_file(certfile),
+                                        password)
 
 def check_xmlsec_import():
     if xmlsec is None:
@@ -141,6 +166,12 @@ def sign_envelope(envelope, keyfile, certfile, password=None):
     </soap:Envelope>
 
     """
+    # Load the signing key and certificate.
+    key = _make_sign_key(_read_file(keyfile), _read_file(certfile), password)
+    return _sign_envelope_with_key(envelope, key)
+
+def _sign_envelope_with_key(envelope, key):
+
     # Create the Signature node.
     signature = xmlsec.template.create(
         envelope,
@@ -155,10 +186,6 @@ def sign_envelope(envelope, keyfile, certfile, password=None):
     xmlsec.template.x509_data_add_issuer_serial(x509_data)
     xmlsec.template.x509_data_add_certificate(x509_data)
 
-    # Load the signing key and certificate.
-    key = xmlsec.Key.from_file(keyfile, xmlsec.KeyFormat.PEM, password=password)
-    key.load_cert_from_file(certfile, xmlsec.KeyFormat.PEM)
-
     # Insert the Signature node in the wsse:Security header.
     security = get_security_header(envelope)
     security.insert(0, signature)
@@ -190,12 +217,19 @@ def verify_envelope(envelope, certfile):
     Expects a document like that found in the sample XML in the ``sign()``
     docstring.
 
-    Raise SignatureValidationFailed on failure, silent on success.
+    Raise SignatureVerificationFailed on failure, silent on success.
 
     """
+    key = _make_verify_key(_read_file(certfile))
+    return _verify_envelope_with_key(envelope, key)
+
+def _verify_envelope_with_key(envelope, key):
     soap_env = detect_soap_env(envelope)
 
     header = envelope.find(QName(soap_env, 'Header'))
+    if not header:
+        raise SignatureVerificationFailed()
+
     security = header.find(QName(ns.WSSE, 'Security'))
     signature = security.find(QName(ns.DS, 'Signature'))
 
@@ -213,7 +247,6 @@ def verify_envelope(envelope, certfile):
         )[0]
         ctx.register_id(referenced, 'Id', ns.WSU)
 
-    key = xmlsec.Key.from_file(certfile, xmlsec.KeyFormat.CERT_PEM, None)
     ctx.key = key
 
     try:
diff --git a/src/zeep/wsse/utils.py b/src/zeep/wsse/utils.py
index d08cfc1..4f96a38 100644
--- a/src/zeep/wsse/utils.py
+++ b/src/zeep/wsse/utils.py
@@ -1,8 +1,8 @@
-from uuid import uuid4
-from lxml import etree
 import datetime
+from uuid import uuid4
 
 import pytz
+from lxml import etree
 from lxml.builder import ElementMaker
 
 from zeep import ns
diff --git a/src/zeep/xsd/__init__.py b/src/zeep/xsd/__init__.py
index 1fc975d..dfe6358 100644
--- a/src/zeep/xsd/__init__.py
+++ b/src/zeep/xsd/__init__.py
@@ -1,4 +1,9 @@
-from zeep.xsd.const import SkipValue  # noqa
+"""
+    zeep.xsd
+    --------
+
+"""
+from zeep.xsd.const import Nil, SkipValue  # noqa
 from zeep.xsd.elements import *  # noqa
 from zeep.xsd.schema import Schema  # noqa
 from zeep.xsd.types import *  # noqa
diff --git a/src/zeep/xsd/const.py b/src/zeep/xsd/const.py
index 7ba420c..75b4097 100644
--- a/src/zeep/xsd/const.py
+++ b/src/zeep/xsd/const.py
@@ -1,15 +1,13 @@
 from lxml import etree
 
-NS_XSI = 'http://www.w3.org/2001/XMLSchema-instance'
-NS_XSD = 'http://www.w3.org/2001/XMLSchema'
-
+from zeep import ns
 
 def xsi_ns(localname):
-    return etree.QName(NS_XSI, localname)
+    return etree.QName(ns.XSI, localname)
 
 
 def xsd_ns(localname):
-    return etree.QName(NS_XSD, localname)
+    return etree.QName(ns.XSD, localname)
 
 
 class _StaticIdentity(object):
@@ -22,3 +20,4 @@ class _StaticIdentity(object):
 
 NotSet = _StaticIdentity('NotSet')
 SkipValue = _StaticIdentity('SkipValue')
+Nil = _StaticIdentity('Nil')
diff --git a/src/zeep/xsd/elements/any.py b/src/zeep/xsd/elements/any.py
index d91c506..e799191 100644
--- a/src/zeep/xsd/elements/any.py
+++ b/src/zeep/xsd/elements/any.py
@@ -2,9 +2,9 @@ import logging
 
 from lxml import etree
 
-from zeep import exceptions
+from zeep import exceptions, ns
 from zeep.utils import qname_attr
-from zeep.xsd.const import xsi_ns, NotSet
+from zeep.xsd.const import NotSet, xsi_ns
 from zeep.xsd.elements.base import Base
 from zeep.xsd.utils import max_occurs_iter
 from zeep.xsd.valueobjects import AnyObject
@@ -84,7 +84,19 @@ class Any(Base):
         return {}
 
     def parse_xmlelements(self, xmlelements, schema, name=None, context=None):
-        """Consume matching xmlelements and call parse() on each of them"""
+        """Consume matching xmlelements and call parse() on each of them
+
+        :param xmlelements: Dequeue of XML element objects
+        :type xmlelements: collections.deque of lxml.etree._Element
+        :param schema: The parent XML schema
+        :type schema: zeep.xsd.Schema
+        :param name: The name of the parent element
+        :type name: str
+        :param context: Optional parsing context (for inline schemas)
+        :type context: zeep.xsd.context.XmlParserContext
+        :return: dict or None
+
+        """
         result = []
 
         for _unused in max_occurs_iter(self.max_occurs):
@@ -169,9 +181,10 @@ class Any(Base):
         # Check if we received a proper value object. If we receive the wrong
         # type then return a nice error message
         if self.restrict:
-            expected_types = (etree._Element,) + self.restrict.accepted_types
+            expected_types = (etree._Element, dict,) + self.restrict.accepted_types
         else:
-            expected_types = (etree._Element, AnyObject)
+            expected_types = (etree._Element,  dict,AnyObject)
+
         if not isinstance(value, expected_types):
             type_names = [
                 '%s.%s' % (t.__module__, t.__name__) for t in expected_types
@@ -188,7 +201,7 @@ class Any(Base):
     def resolve(self):
         return self
 
-    def signature(self, depth=()):
+    def signature(self, schema=None, standalone=True):
         if self.restrict:
             base = self.restrict.name
         else:
@@ -201,23 +214,30 @@ class Any(Base):
 
 class AnyAttribute(Base):
     name = None
+    _ignore_attributes = [
+        etree.QName(ns.XSI, 'type')
+    ]
 
     def __init__(self, process_contents='strict'):
         self.qname = None
         self.process_contents = process_contents
 
     def parse(self, attributes, context=None):
-        return attributes
+        result = {}
+        for key, value in attributes.items():
+            if key not in self._ignore_attributes:
+                result[key] = value
+        return result
 
     def resolve(self):
         return self
 
     def render(self, parent, value, render_path=None):
-        if value is None:
+        if value in (None, NotSet):
             return
 
         for name, val in value.items():
             parent.set(name, val)
 
-    def signature(self, depth=()):
+    def signature(self, schema=None, standalone=True):
         return '{}'
diff --git a/src/zeep/xsd/elements/attribute.py b/src/zeep/xsd/elements/attribute.py
index 04ec751..5ca075a 100644
--- a/src/zeep/xsd/elements/attribute.py
+++ b/src/zeep/xsd/elements/attribute.py
@@ -88,5 +88,5 @@ class AttributeGroup(object):
         self._attributes = resolved
         return self
 
-    def signature(self, depth=()):
-        return ', '.join(attr.signature() for attr in self._attributes)
+    def signature(self, schema=None, standalone=True):
+        return ', '.join(attr.signature(schema) for attr in self._attributes)
diff --git a/src/zeep/xsd/elements/base.py b/src/zeep/xsd/elements/base.py
index 64ff835..499ccac 100644
--- a/src/zeep/xsd/elements/base.py
+++ b/src/zeep/xsd/elements/base.py
@@ -25,8 +25,20 @@ class Base(object):
         raise NotImplementedError()
 
     def parse_xmlelements(self, xmlelements, schema, name=None, context=None):
-        """Consume matching xmlelements and call parse() on each of them"""
+        """Consume matching xmlelements and call parse() on each of them
+
+        :param xmlelements: Dequeue of XML element objects
+        :type xmlelements: collections.deque of lxml.etree._Element
+        :param schema: The parent XML schema
+        :type schema: zeep.xsd.Schema
+        :param name: The name of the parent element
+        :type name: str
+        :param context: Optional parsing context (for inline schemas)
+        :type context: zeep.xsd.context.XmlParserContext
+        :return: dict or None
+
+        """
         raise NotImplementedError()
 
-    def signature(self, depth=()):
+    def signature(self, schema=None, standalone=False):
         return ''
diff --git a/src/zeep/xsd/elements/builtins.py b/src/zeep/xsd/elements/builtins.py
index 5eca4c6..eedf460 100644
--- a/src/zeep/xsd/elements/builtins.py
+++ b/src/zeep/xsd/elements/builtins.py
@@ -35,6 +35,6 @@ class Schema(Base):
         return self
 
 
-default_elements = {
-    xsd_ns('schema'): Schema(),
-}
+_elements = [
+    Schema
+]
diff --git a/src/zeep/xsd/elements/element.py b/src/zeep/xsd/elements/element.py
index ebb1739..204e532 100644
--- a/src/zeep/xsd/elements/element.py
+++ b/src/zeep/xsd/elements/element.py
@@ -6,10 +6,10 @@ from lxml import etree
 from zeep import exceptions
 from zeep.exceptions import UnexpectedElementError
 from zeep.utils import qname_attr
-from zeep.xsd.const import NotSet, xsi_ns
+from zeep.xsd.const import Nil, NotSet, xsi_ns
 from zeep.xsd.context import XmlParserContext
 from zeep.xsd.elements.base import Base
-from zeep.xsd.utils import max_occurs_iter
+from zeep.xsd.utils import create_prefixed_name, max_occurs_iter
 
 logger = logging.getLogger(__name__)
 
@@ -38,7 +38,10 @@ class Element(Base):
 
     def __str__(self):
         if self.type:
-            return '%s(%s)' % (self.name, self.type.signature())
+            if self.type.is_global:
+                return '%s(%s)' % (self.name, self.type.qname)
+            else:
+                return '%s(%s)' % (self.name, self.type.signature())
         return '%s()' % self.name
 
     def __call__(self, *args, **kwargs):
@@ -57,10 +60,16 @@ class Element(Base):
             self.__class__ == other.__class__ and
             self.__dict__ == other.__dict__)
 
+    def get_prefixed_name(self, schema):
+        return create_prefixed_name(self.qname, schema)
+
     @property
     def default_value(self):
-        value = [] if self.accepts_multiple else self.default
-        return value
+        if self.accepts_multiple:
+            return []
+        if self.is_optional:
+            return None
+        return self.default
 
     def clone(self, name=None, min_occurs=1, max_occurs=1):
         new = copy.copy(self)
@@ -81,6 +90,18 @@ class Element(Base):
         use that for further processing. This should only be done for subtypes
         of the defined type but for now we just accept everything.
 
+        This is the entrypoint for parsing an xml document.
+
+        :param xmlelement: The XML element to parse
+        :type xmlelements: lxml.etree._Element
+        :param schema: The parent XML schema
+        :type schema: zeep.xsd.Schema
+        :param allow_none: Allow none
+        :type allow_none: bool
+        :param context: Optional parsing context (for inline schemas)
+        :type context: zeep.xsd.context.XmlParserContext
+        :return: dict or None
+
         """
         context = context or XmlParserContext()
         instance_type = qname_attr(xmlelement, xsi_ns('type'))
@@ -89,14 +110,27 @@ class Element(Base):
             xsd_type = schema.get_type(instance_type, fail_silently=True)
         xsd_type = xsd_type or self.type
         return xsd_type.parse_xmlelement(
-            xmlelement, schema, allow_none=allow_none, context=context)
+            xmlelement, schema, allow_none=allow_none, context=context,
+            schema_type=self.type)
 
     def parse_kwargs(self, kwargs, name, available_kwargs):
         return self.type.parse_kwargs(
             kwargs, name or self.attr_name, available_kwargs)
 
     def parse_xmlelements(self, xmlelements, schema, name=None, context=None):
-        """Consume matching xmlelements and call parse() on each of them"""
+        """Consume matching xmlelements and call parse() on each of them
+
+        :param xmlelements: Dequeue of XML element objects
+        :type xmlelements: collections.deque of lxml.etree._Element
+        :param schema: The parent XML schema
+        :type schema: zeep.xsd.Schema
+        :param name: The name of the parent element
+        :type name: str
+        :param context: Optional parsing context (for inline schemas)
+        :type context: zeep.xsd.context.XmlParserContext
+        :return: dict or None
+
+        """
         result = []
         num_matches = 0
         for _unused in max_occurs_iter(self.max_occurs):
@@ -113,7 +147,8 @@ class Element(Base):
             element_tag = etree.QName(xmlelements[0].tag)
             if (
                 element_tag.namespace and self.qname.namespace and
-                element_tag.namespace != self.qname.namespace
+                element_tag.namespace != self.qname.namespace and
+                schema.strict
             ):
                 break
 
@@ -123,8 +158,7 @@ class Element(Base):
                 num_matches += 1
                 item = self.parse(
                     xmlelement, schema, allow_none=True, context=context)
-                if item is not None:
-                    result.append(item)
+                result.append(item)
             else:
                 # If the element passed doesn't match and the current one is
                 # not optional then throw an error
@@ -158,6 +192,12 @@ class Element(Base):
 
     def _render_value_item(self, parent, value, render_path):
         """Render the value on the parent lxml.Element"""
+
+        if value is Nil:
+            elm = etree.SubElement(parent, self.qname)
+            elm.set(xsi_ns('nil'), 'true')
+            return
+
         if value is None or value is NotSet:
             if self.is_optional:
                 return
@@ -215,11 +255,19 @@ class Element(Base):
         self.resolve_type()
         return self
 
-    def signature(self, depth=()):
-        if len(depth) > 0 and self.is_global:
-            return self.name + '()'
+    def signature(self, schema=None, standalone=True):
+        from zeep.xsd import ComplexType
+        if self.type.is_global or (not standalone and self.is_global):
+            value = self.type.get_prefixed_name(schema)
+        else:
+            value = self.type.signature(schema, standalone=False)
+
+            if not standalone and isinstance(self.type, ComplexType):
+                value = '{%s}' % value
+
+        if standalone:
+            value = '%s(%s)' % (self.get_prefixed_name(schema), value)
 
-        value = self.type.signature(depth)
         if self.accepts_multiple:
             return '%s[]' % value
         return value
diff --git a/src/zeep/xsd/elements/indicators.py b/src/zeep/xsd/elements/indicators.py
index 282b13a..10ccf00 100644
--- a/src/zeep/xsd/elements/indicators.py
+++ b/src/zeep/xsd/elements/indicators.py
@@ -1,36 +1,56 @@
+"""
+zeep.xsd.elements.indicators
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Indicators are a collection of elements. There are four available, these are
+All, Choice, Group and Sequence.
+
+    Indicator -> OrderIndicator -> All
+                                -> Choice
+                                -> Sequence
+              -> Group
+
+"""
 import copy
 import operator
 from collections import OrderedDict, defaultdict, deque
 
 from cached_property import threaded_cached_property
 
-from zeep.exceptions import UnexpectedElementError
+from zeep.exceptions import UnexpectedElementError, ValidationError
 from zeep.xsd.const import NotSet, SkipValue
 from zeep.xsd.elements import Any, Element
 from zeep.xsd.elements.base import Base
 from zeep.xsd.utils import (
-    NamePrefixGenerator, UniqueNameGenerator, max_occurs_iter)
+    NamePrefixGenerator, UniqueNameGenerator, create_prefixed_name,
+    max_occurs_iter)
 
 __all__ = ['All', 'Choice', 'Group', 'Sequence']
 
 
 class Indicator(Base):
+    """Base class for the other indicators"""
 
     def __repr__(self):
         return '<%s(%s)>' % (
             self.__class__.__name__, super(Indicator, self).__repr__())
 
-    @threaded_cached_property
+    @property
     def default_value(self):
-        return OrderedDict([
+        values = OrderedDict([
             (name, element.default_value) for name, element in self.elements
         ])
 
+        if self.accepts_multiple:
+            return {'_value_1': values}
+        return values
+
     def clone(self, name, min_occurs=1, max_occurs=1):
         raise NotImplementedError()
 
 
 class OrderIndicator(Indicator, list):
+    """Base class for All, Choice and Sequence classes."""
     name = None
 
     def __init__(self, elements=None, min_occurs=1, max_occurs=1):
@@ -85,16 +105,30 @@ class OrderIndicator(Indicator, list):
         If not all required elements are available then 0 is returned.
 
         """
-        num = 0
-        for name, element in self.elements_nested:
-            if isinstance(element, Element):
-                if element.name in values and values[element.name] is not None:
-                    num += 1
-            else:
-                num += element.accept(values)
-        return num
+        if not self.accepts_multiple:
+            values = [values]
+
+        results = set()
+        for value in values:
+            num = 0
+            for name, element in self.elements_nested:
+                if isinstance(element, Element):
+                    if element.name in value and value[element.name] is not None:
+                        num += 1
+                else:
+                    num += element.accept(value)
+            results.add(num)
+        return max(results)
 
     def parse_args(self, args, index=0):
+
+        # If the sequence contains an choice element then we can't convert
+        # the args to kwargs since Choice elements don't work with position
+        # arguments
+        for name, elm in self.elements_nested:
+            if isinstance(elm, Choice):
+                raise TypeError("Choice elements only work with keyword arguments")
+
         result = {}
         for name, element in self.elements:
             if index >= len(args):
@@ -110,11 +144,24 @@ class OrderIndicator(Indicator, list):
         The available_kwargs is modified in-place. Returns a dict with the
         result.
 
+        :param kwargs: The kwargs
+        :type kwargs: dict
+        :param name: The name as which this type is registered in the parent
+        :type name: str
+        :param available_kwargs: The kwargs keys which are still available,
+         modified in place
+        :type available_kwargs: set
+        :rtype: dict
+
         """
         if self.accepts_multiple:
             assert name
 
-        if name and name in available_kwargs:
+        if name:
+            if name not in available_kwargs:
+                return {}
+
+            assert self.accepts_multiple
 
             # Make sure we have a list, lame lame
             item_kwargs = kwargs.get(name)
@@ -123,19 +170,26 @@ class OrderIndicator(Indicator, list):
 
             result = []
             for item_value in max_occurs_iter(self.max_occurs, item_kwargs):
-                item_kwargs = set(item_value.keys())
+                try:
+                    item_kwargs = set(item_value.keys())
+                except AttributeError:
+                    raise TypeError(
+                        "A list of dicts is expected for unbounded Sequences")
+
                 subresult = OrderedDict()
                 for item_name, element in self.elements:
                     value = element.parse_kwargs(item_value, item_name, item_kwargs)
                     if value is not None:
                         subresult.update(value)
 
+                if item_kwargs:
+                    raise TypeError((
+                        "%s() got an unexpected keyword argument %r."
+                    ) % (self, list(item_kwargs)[0]))
+
                 result.append(subresult)
 
-            if self.accepts_multiple:
-                result = {name: result}
-            else:
-                result = result[0] if result else None
+            result = {name: result}
 
             # All items consumed
             if not any(filter(None, item_kwargs)):
@@ -144,15 +198,13 @@ class OrderIndicator(Indicator, list):
             return result
 
         else:
+            assert not self.accepts_multiple
             result = OrderedDict()
             for elm_name, element in self.elements_nested:
                 sub_result = element.parse_kwargs(kwargs, elm_name, available_kwargs)
                 if sub_result:
                     result.update(sub_result)
 
-            if name:
-                result = {name: result}
-
             return result
 
     def resolve(self):
@@ -167,6 +219,8 @@ class OrderIndicator(Indicator, list):
         else:
             values = value
 
+        self.validate(values, render_path)
+
         for value in max_occurs_iter(self.max_occurs, values):
             for name, element in self.elements_nested:
                 if name:
@@ -186,22 +240,20 @@ class OrderIndicator(Indicator, list):
                 if element_value is not None or not element.is_optional:
                     element.render(parent, element_value, child_path)
 
-    def signature(self, depth=()):
-        """
-        Use a tuple of element names as depth indicator, so that when an element is repeated,
-        do not try to create its signature, as it would lead to infinite recursion
-        """
-        depth += (self.name,)
+    def validate(self, value, render_path):
+        for item in value:
+            if item is NotSet:
+                raise ValidationError("No value set", path=render_path)
+
+    def signature(self, schema=None, standalone=True):
         parts = []
         for name, element in self.elements_nested:
-            if hasattr(element, 'type') and element.type.name and element.type.name in depth:
-                parts.append('{}: {}'.format(name, element.type.name))
-            elif name:
-                parts.append('%s: %s' % (name, element.signature(depth)))
-            elif isinstance(element,  Indicator):
-                parts.append('%s' % (element.signature(depth)))
+            if isinstance(element,  Indicator):
+                parts.append(element.signature(schema, standalone=False))
             else:
-                parts.append('%s: %s' % (name, element.signature(depth)))
+                value = element.signature(schema, standalone=False)
+                parts.append('%s: %s' % (name, value))
+
         part = ', '.join(parts)
 
         if self.accepts_multiple:
@@ -215,7 +267,25 @@ class All(OrderIndicator):
 
     """
 
+    def __init__(self, elements=None, min_occurs=1, max_occurs=1,
+                 consume_other=False):
+        super(All, self).__init__(elements, min_occurs, max_occurs)
+        self._consume_other = consume_other
+
     def parse_xmlelements(self, xmlelements, schema, name=None, context=None):
+        """Consume matching xmlelements
+
+        :param xmlelements: Dequeue of XML element objects
+        :type xmlelements: collections.deque of lxml.etree._Element
+        :param schema: The parent XML schema
+        :type schema: zeep.xsd.Schema
+        :param name: The name of the parent element
+        :type name: str
+        :param context: Optional parsing context (for inline schemas)
+        :type context: zeep.xsd.context.XmlParserContext
+        :rtype: dict or None
+
+        """
         result = OrderedDict()
         expected_tags = {element.qname for __, element in self.elements}
         consumed_tags = set()
@@ -236,10 +306,18 @@ class All(OrderIndicator):
                 result[name] = element.parse_xmlelements(
                     sub_elements, schema, context=context)
 
+        if self._consume_other and xmlelements:
+            result['_raw_elements'] = list(xmlelements)
+            xmlelements.clear()
         return result
 
 
 class Choice(OrderIndicator):
+    """Permits one and only one of the elements contained in the group."""
+
+    def parse_args(self, args, index=0):
+        if args:
+            raise TypeError("Choice elements only work with keyword arguments")
 
     @property
     def is_optional(self):
@@ -250,49 +328,59 @@ class Choice(OrderIndicator):
         return OrderedDict()
 
     def parse_xmlelements(self, xmlelements, schema, name=None, context=None):
-        """Return a dictionary"""
+        """Consume matching xmlelements
+
+        :param xmlelements: Dequeue of XML element objects
+        :type xmlelements: collections.deque of lxml.etree._Element
+        :param schema: The parent XML schema
+        :type schema: zeep.xsd.Schema
+        :param name: The name of the parent element
+        :type name: str
+        :param context: Optional parsing context (for inline schemas)
+        :type context: zeep.xsd.context.XmlParserContext
+        :rtype: dict or None
+
+        """
         result = []
 
         for _unused in max_occurs_iter(self.max_occurs):
             if not xmlelements:
                 break
 
-            for node in list(xmlelements):
-
-                # Choose out of multiple
-                options = []
-                for element_name, element in self.elements_nested:
+            # Choose out of multiple
+            options = []
+            for element_name, element in self.elements_nested:
 
-                    local_xmlelements = copy.copy(xmlelements)
+                local_xmlelements = copy.copy(xmlelements)
 
-                    try:
-                        sub_result = element.parse_xmlelements(
-                            local_xmlelements, schema, context=context)
-                    except UnexpectedElementError:
-                        continue
+                try:
+                    sub_result = element.parse_xmlelements(
+                        xmlelements=local_xmlelements,
+                        schema=schema,
+                        name=element_name,
+                        context=context)
+                except UnexpectedElementError:
+                    continue
 
-                    if isinstance(element, OrderIndicator):
-                        if element.accepts_multiple:
-                            sub_result = {element_name: sub_result}
-                    else:
-                        sub_result = {element_name: sub_result}
+                if isinstance(element, Element):
+                    sub_result = {element_name: sub_result}
 
-                    num_consumed = len(xmlelements) - len(local_xmlelements)
-                    if num_consumed:
-                        options.append((num_consumed, sub_result))
+                num_consumed = len(xmlelements) - len(local_xmlelements)
+                if num_consumed:
+                    options.append((num_consumed, sub_result))
 
-                if not options:
-                    xmlelements = []
-                    break
+            if not options:
+                xmlelements = []
+                break
 
-                # Sort on least left
-                options = sorted(options, key=operator.itemgetter(0), reverse=True)
-                if options:
-                    result.append(options[0][1])
-                    for i in range(options[0][0]):
-                        xmlelements.popleft()
-                else:
-                    break
+            # Sort on least left
+            options = sorted(options, key=operator.itemgetter(0), reverse=True)
+            if options:
+                result.append(options[0][1])
+                for i in range(options[0][0]):
+                    xmlelements.popleft()
+            else:
+                break
 
         if self.accepts_multiple:
             result = {name: result}
@@ -308,7 +396,7 @@ class Choice(OrderIndicator):
         This handles two distinct initialization methods:
 
         1. Passing the choice elements directly to the kwargs (unnested)
-        2. Passing the choice elements into the `name` kwarg (_alue_1) (nested).
+        2. Passing the choice elements into the `name` kwarg (_value_1) (nested).
            This case is required when multiple choice elements are given.
 
         :param name: Name of the choice element (_value_1)
@@ -320,6 +408,8 @@ class Choice(OrderIndicator):
 
         """
         if name and name in available_kwargs:
+            assert self.accepts_multiple
+
             values = kwargs[name] or []
             available_kwargs.remove(name)
             result = []
@@ -327,9 +417,9 @@ class Choice(OrderIndicator):
             if isinstance(values, dict):
                 values = [values]
 
+            # TODO: Use most greedy choice instead of first matching
             for value in values:
                 for element in self:
-                    # TODO: Use most greedy choice instead of first matching
                     if isinstance(element, OrderIndicator):
                         choice_value = value[name] if name in value else value
                         if element.accept(choice_value):
@@ -356,9 +446,9 @@ class Choice(OrderIndicator):
 
             # When choice elements are specified directly in the kwargs
             found = False
-            for i, choice in enumerate(self):
+            for name, choice in self.elements_nested:
                 temp_kwargs = copy.copy(available_kwargs)
-                subresult = choice.parse_kwargs(kwargs, None, temp_kwargs)
+                subresult = choice.parse_kwargs(kwargs, name, temp_kwargs)
 
                 if subresult:
                     if not any(subresult.values()):
@@ -388,12 +478,24 @@ class Choice(OrderIndicator):
         if not self.accepts_multiple:
             value = [value]
 
+        self.validate(value, render_path)
+
         for item in value:
             result = self._find_element_to_render(item)
             if result:
                 element, choice_value = result
                 element.render(parent, choice_value, render_path)
 
+    def validate(self, value, render_path):
+        found = 0
+        for item in value:
+            result = self._find_element_to_render(item)
+            if result:
+                found += 1
+
+        if not found and not self.is_optional:
+            raise ValidationError("Missing choice values", path=render_path)
+
     def accept(self, values):
         """Return the number of values which are accepted by this choice.
 
@@ -403,15 +505,24 @@ class Choice(OrderIndicator):
         nums = set()
         for name, element in self.elements_nested:
             if isinstance(element, Element):
-                if name in values and values[name]:
-                    nums.add(1)
+                if self.accepts_multiple:
+                    if all(name in item and item[name] for item in values):
+                        nums.add(1)
+                else:
+                    if name in values and values[name]:
+                        nums.add(1)
             else:
                 num = element.accept(values)
                 nums.add(num)
         return max(nums) if nums else 0
 
     def _find_element_to_render(self, value):
-        """Return a tuple (element, value) for the best matching choice"""
+        """Return a tuple (element, value) for the best matching choice.
+
+        This is used to decide which choice child is best suitable for
+        rendering the available data.
+
+        """
         matches = []
 
         for name, element in self.elements_nested:
@@ -441,13 +552,13 @@ class Choice(OrderIndicator):
             matches = sorted(matches, key=operator.itemgetter(0), reverse=True)
             return matches[0][1:]
 
-    def signature(self, depth=()):
+    def signature(self, schema=None, standalone=True):
         parts = []
         for name, element in self.elements_nested:
             if isinstance(element, OrderIndicator):
-                parts.append('{%s}' % (element.signature(depth)))
+                parts.append('{%s}' % (element.signature(schema, standalone=False)))
             else:
-                parts.append('{%s: %s}' % (name, element.signature(depth)))
+                parts.append('{%s: %s}' % (name, element.signature(schema, standalone=False)))
         part = '(%s)' % ' | '.join(parts)
         if self.accepts_multiple:
             return '%s[]' % (part,)
@@ -455,17 +566,42 @@ class Choice(OrderIndicator):
 
 
 class Sequence(OrderIndicator):
+    """Requires the elements in the group to appear in the specified sequence
+    within the containing element.
 
+    """
     def parse_xmlelements(self, xmlelements, schema, name=None, context=None):
+        """Consume matching xmlelements
+
+        :param xmlelements: Dequeue of XML element objects
+        :type xmlelements: collections.deque of lxml.etree._Element
+        :param schema: The parent XML schema
+        :type schema: zeep.xsd.Schema
+        :param name: The name of the parent element
+        :type name: str
+        :param context: Optional parsing context (for inline schemas)
+        :type context: zeep.xsd.context.XmlParserContext
+        :rtype: dict or None
+
+        """
         result = []
+
+        if self.accepts_multiple:
+            assert name
+
         for _unused in max_occurs_iter(self.max_occurs):
             if not xmlelements:
                 break
 
             item_result = OrderedDict()
             for elm_name, element in self.elements:
-                item_subresult = element.parse_xmlelements(
-                    xmlelements, schema, name, context=context)
+                try:
+                    item_subresult = element.parse_xmlelements(
+                        xmlelements, schema, name, context=context)
+                except UnexpectedElementError:
+                    if schema.strict:
+                        raise
+                    item_subresult = None
 
                 # Unwrap if allowed
                 if isinstance(element, OrderIndicator):
@@ -480,7 +616,6 @@ class Sequence(OrderIndicator):
 
         if not self.accepts_multiple:
             return result[0] if result else None
-
         return {name: result}
 
 
@@ -498,15 +633,8 @@ class Group(Indicator):
         self.max_occurs = max_occurs
         self.min_occurs = min_occurs
 
-    def clone(self, name, min_occurs=1, max_occurs=1):
-        return self.__class__(
-            name=self.qname,
-            child=self.child,
-            min_occurs=min_occurs,
-            max_occurs=max_occurs)
-
     def __str__(self):
-        return '%s(%s)' % (self.name, self.signature())
+        return self.signature()
 
     def __iter__(self, *args, **kwargs):
         for item in self.child:
@@ -518,13 +646,28 @@ class Group(Indicator):
             return [('_value_1', self.child)]
         return self.child.elements
 
+    def clone(self, name, min_occurs=1, max_occurs=1):
+        return self.__class__(
+            name=name,
+            child=self.child,
+            min_occurs=min_occurs,
+            max_occurs=max_occurs)
+
+    def accept(self, values):
+        """Return the number of values which are accepted by this choice.
+
+        If not all required elements are available then 0 is returned.
+
+        """
+        return self.child.accept(values)
+
     def parse_args(self, args, index=0):
         return self.child.parse_args(args, index)
 
     def parse_kwargs(self, kwargs, name, available_kwargs):
         if self.accepts_multiple:
             if name not in kwargs:
-                return {}, kwargs
+                return {}
 
             available_kwargs.remove(name)
             item_kwargs = kwargs[name]
@@ -536,6 +679,11 @@ class Group(Indicator):
                 subresult = self.child.parse_kwargs(
                     sub_kwargs, sub_name, available_sub_kwargs)
 
+                if available_sub_kwargs:
+                    raise TypeError((
+                        "%s() got an unexpected keyword argument %r."
+                    ) % (self, list(available_sub_kwargs)[0]))
+
                 if subresult:
                     result.append(subresult)
             if result:
@@ -545,6 +693,19 @@ class Group(Indicator):
         return result
 
     def parse_xmlelements(self, xmlelements, schema, name=None, context=None):
+        """Consume matching xmlelements
+
+        :param xmlelements: Dequeue of XML element objects
+        :type xmlelements: collections.deque of lxml.etree._Element
+        :param schema: The parent XML schema
+        :type schema: zeep.xsd.Schema
+        :param name: The name of the parent element
+        :type name: str
+        :param context: Optional parsing context (for inline schemas)
+        :type context: zeep.xsd.context.XmlParserContext
+        :rtype: dict or None
+
+        """
         result = []
 
         for _unused in max_occurs_iter(self.max_occurs):
@@ -552,16 +713,29 @@ class Group(Indicator):
                 self.child.parse_xmlelements(
                     xmlelements, schema, name, context=context)
             )
+            if not xmlelements:
+                break
         if not self.accepts_multiple and result:
             return result[0]
         return {name: result}
 
-    def render(self, *args, **kwargs):
-        return self.child.render(*args, **kwargs)
+    def render(self, parent, value, render_path):
+        if not isinstance(value, list):
+            values = [value]
+        else:
+            values = value
+
+        for value in values:
+            self.child.render(parent, value, render_path)
 
     def resolve(self):
         self.child = self.child.resolve()
         return self
 
-    def signature(self, depth=()):
-        return self.child.signature(depth)
+    def signature(self, schema=None, standalone=True):
+        name = create_prefixed_name(self.qname, schema)
+        if standalone:
+            return '%s(%s)' % (
+                name, self.child.signature(schema, standalone=False))
+        else:
+            return self.child.signature(schema, standalone=False)
diff --git a/src/zeep/xsd/elements/references.py b/src/zeep/xsd/elements/references.py
index d3eb1ce..4aa30b5 100644
--- a/src/zeep/xsd/elements/references.py
+++ b/src/zeep/xsd/elements/references.py
@@ -1,8 +1,15 @@
+"""
+zeep.xsd.elements.references
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Ref* objecs are only used temporarily between parsing the schema and resolving
+all the elements.
+
+"""
 __all__ = ['RefElement', 'RefAttribute', 'RefAttributeGroup', 'RefGroup']
 
 
 class RefElement(object):
-
     def __init__(self, tag, ref, schema, is_qualified=False,
                  min_occurs=1, max_occurs=1):
         self._ref = ref
@@ -37,4 +44,7 @@ class RefAttributeGroup(RefElement):
 
 class RefGroup(RefElement):
     def resolve(self):
-        return self._schema.get_group(self._ref)
+        elm = self._schema.get_group(self._ref)
+        elm = elm.clone(
+            elm.qname, min_occurs=self.min_occurs, max_occurs=self.max_occurs)
+        return elm
diff --git a/src/zeep/xsd/schema.py b/src/zeep/xsd/schema.py
index 93993c1..8026009 100644
--- a/src/zeep/xsd/schema.py
+++ b/src/zeep/xsd/schema.py
@@ -3,8 +3,7 @@ from collections import OrderedDict
 
 from lxml import etree
 
-from zeep import exceptions
-from zeep.xsd import const
+from zeep import exceptions, ns
 from zeep.xsd.elements import builtins as xsd_builtins_elements
 from zeep.xsd.types import builtins as xsd_builtins_types
 from zeep.xsd.visitor import SchemaVisitor
@@ -15,13 +14,24 @@ logger = logging.getLogger(__name__)
 class Schema(object):
     """A schema is a collection of schema documents."""
 
-    def __init__(self, node=None, transport=None, location=None):
+    def __init__(self, node=None, transport=None, location=None, strict=True):
+        """
+        :param node:
+        :param transport:
+        :param location:
+        :param strict: Boolean to indicate if the parsing is strict (default)
+
+        """
+        self.strict = strict
+
         self._transport = transport
 
         self._documents = OrderedDict()
         self._prefix_map_auto = {}
         self._prefix_map_custom = {}
 
+        self._load_default_documents()
+
         if not isinstance(node, list):
             nodes = [node] if node is not None else []
         else:
@@ -45,6 +55,12 @@ class Schema(object):
         return retval
 
     @property
+    def root_document(self):
+        return next(
+            (doc for doc in self.documents if not doc._is_internal),
+            None)
+
+    @property
     def is_empty(self):
         """Boolean to indicate if this schema contains any types or elements"""
         return all(document.is_empty for document in self.documents)
@@ -55,25 +71,38 @@ class Schema(object):
 
     @property
     def elements(self):
-        """Yield all globla xsd.Type objects"""
+        """Yield all globla xsd.Type objects
+
+        :rtype: Iterable of zeep.xsd.Element
+
+        """
+        seen = set()
         for document in self.documents:
             for element in document._elements.values():
-                yield element
+                if element.qname not in seen:
+                    yield element
+                    seen.add(element.qname)
 
     @property
     def types(self):
-        """Yield all globla xsd.Type objects"""
+        """Yield all global xsd.Type objects
+
+        :rtype: Iterable of zeep.xsd.ComplexType
+
+        """
+        seen = set()
         for document in self.documents:
             for type_ in document._types.values():
-                yield type_
+                if type_.qname not in seen:
+                    yield type_
+                    seen.add(type_.qname)
 
     def __repr__(self):
-        if self._documents:
-            main_doc = next(self.documents)
-            location = main_doc._location
-        else:
-            location = '<none>'
-        return '<Schema(location=%r)>' % location
+        main_doc = self.root_document
+        if main_doc:
+            return '<Schema(location=%r, tns=%r)>' % (
+                main_doc._location, main_doc._target_namespace)
+        return '<Schema()>'
 
     def add_documents(self, schema_nodes, location):
         documents = []
@@ -87,49 +116,51 @@ class Schema(object):
         self._prefix_map_auto = self._create_prefix_map()
 
     def get_element(self, qname):
-        """Return a global xsd.Element object with the given qname"""
-        qname = self._create_qname(qname)
-        if qname.text in xsd_builtins_elements.default_elements:
-            return xsd_builtins_elements.default_elements[qname]
+        """Return a global xsd.Element object with the given qname
 
-        # Handle XSD namespace items
-        if qname.namespace == const.NS_XSD:
-            try:
-                return xsd_builtins_elements.default_elements[qname]
-            except KeyError:
-                raise exceptions.LookupError("No such type %r" % qname.text)
+        :rtype: zeep.xsd.Group
 
+        """
+        qname = self._create_qname(qname)
         return self._get_instance(qname, 'get_element', 'element')
 
     def get_type(self, qname, fail_silently=False):
-        """Return a global xsd.Type object with the given qname"""
-        qname = self._create_qname(qname)
+        """Return a global xsd.Type object with the given qname
 
-        # Handle XSD namespace items
-        if qname.namespace == const.NS_XSD:
-            try:
-                return xsd_builtins_types.default_types[qname]
-            except KeyError:
-                raise exceptions.LookupError("No such type %r" % qname.text)
+        :rtype: zeep.xsd.ComplexType or zeep.xsd.AnySimpleType
 
+        """
+        qname = self._create_qname(qname)
         try:
             return self._get_instance(qname, 'get_type', 'type')
         except exceptions.NamespaceError as exc:
             if fail_silently:
-                logger.info(str(exc))
+                logger.debug(str(exc))
             else:
                 raise
 
     def get_group(self, qname):
-        """Return a global xsd.Group object with the given qname"""
+        """Return a global xsd.Group object with the given qname.
+
+        :rtype: zeep.xsd.Group
+
+        """
         return self._get_instance(qname, 'get_group', 'group')
 
     def get_attribute(self, qname):
-        """Return a global xsd.attributeGroup object with the given qname"""
+        """Return a global xsd.attributeGroup object with the given qname
+
+        :rtype: zeep.xsd.Attribute
+
+        """
         return self._get_instance(qname, 'get_attribute', 'attribute')
 
     def get_attribute_group(self, qname):
-        """Return a global xsd.attributeGroup object with the given qname"""
+        """Return a global xsd.attributeGroup object with the given qname
+
+        :rtype: zeep.xsd.AttributeGroup
+
+        """
         return self._get_instance(qname, 'get_attribute_group', 'attributeGroup')
 
     def set_ns_prefix(self, prefix, namespace):
@@ -144,6 +175,18 @@ class Schema(object):
         except KeyError:
             raise ValueError("No such prefix %r" % prefix)
 
+    def get_shorthand_for_ns(self, namespace):
+        for prefix, other_namespace in self._prefix_map_auto.items():
+            if namespace == other_namespace:
+                return prefix
+        for prefix, other_namespace in self._prefix_map_custom.items():
+            if namespace == other_namespace:
+                return prefix
+
+        if namespace == 'http://schemas.xmlsoap.org/soap/envelope/':
+            return 'soap-env'
+        return namespace
+
     def create_new_document(self, node, url, base_url=None):
         namespace = node.get('targetNamespace') if node is not None else None
         if base_url is None:
@@ -160,6 +203,21 @@ class Schema(object):
             self._add_schema_document(document)
         self._prefix_map_auto = self._create_prefix_map()
 
+    def _load_default_documents(self):
+        schema = SchemaDocument(ns.XSD, None, None)
+
+        for cls in xsd_builtins_types._types:
+            instance = cls(is_global=True)
+            schema.register_type(cls._default_qname, instance)
+
+        for cls in xsd_builtins_elements._elements:
+            instance = cls()
+            schema.register_element(cls.qname, instance)
+
+        schema._is_internal = True
+        self._add_schema_document(schema)
+        return schema
+
     def _get_instance(self, qname, method_name, name):
         """Return an object from one of the SchemaDocument's"""
         qname = self._create_qname(qname)
@@ -185,6 +243,8 @@ class Schema(object):
 
         This also expands the shorthand notation.
 
+        :rtype: lxml.etree.QNaame
+
         """
         if isinstance(name, etree.QName):
             return name
@@ -205,26 +265,45 @@ class Schema(object):
         prefix_map = {
             'xsd': 'http://www.w3.org/2001/XMLSchema',
         }
-        for i, namespace in enumerate(self._documents.keys()):
-            if namespace is None:
+        i = 0
+        for namespace in self._documents.keys():
+            if namespace is None or namespace in prefix_map.values():
                 continue
+
             prefix_map['ns%d' % i] = namespace
+            i += 1
         return prefix_map
 
     def _has_schema_document(self, namespace):
+        """Return a boolean if there is a SchemaDocumnet for the namespace.
+
+        :rtype: boolean
+
+        """
         return namespace in self._documents
 
     def _add_schema_document(self, document):
-        logger.info("Add document with tns %s to schema %s", document.namespace, id(self))
+        logger.debug("Add document with tns %s to schema %s", document.namespace, id(self))
         documents = self._documents.setdefault(document.namespace, [])
         documents.append(document)
 
     def _get_schema_document(self, namespace, location):
+        """Return a list of SchemaDocument's for the given namespace AND
+        location.
+
+        :rtype: SchemaDocument
+
+        """
         for document in self._documents.get(namespace, []):
             if document._location == location:
                 return document
 
     def _get_schema_documents(self, namespace, fail_silently=False):
+        """Return a list of SchemaDocument's for the given namespace.
+
+        :rtype: list of SchemaDocument
+
+        """
         if namespace not in self._documents:
             if fail_silently:
                 return []
@@ -241,7 +320,7 @@ class SchemaDocument(object):
         self._base_url = base_url or location
         self._location = location
         self._target_namespace = namespace
-        self._elm_instances = []
+        self._is_internal = False
 
         self._attribute_groups = {}
         self._attributes = {}
@@ -283,7 +362,7 @@ class SchemaDocument(object):
         visitor.visit_schema(node)
 
     def resolve(self):
-        logger.info("Resolving in schema %s", self)
+        logger.debug("Resolving in schema %s", self)
 
         if self._resolved:
             return
@@ -294,10 +373,23 @@ class SchemaDocument(object):
                 schema.resolve()
 
         def _resolve_dict(val):
-            for key, obj in val.items():
-                new = obj.resolve()
-                assert new is not None, "resolve() should return an object"
-                val[key] = new
+            try:
+                for key, obj in val.items():
+                    new = obj.resolve()
+                    assert new is not None, "resolve() should return an object"
+                    val[key] = new
+            except exceptions.LookupError as exc:
+                raise exceptions.LookupError(
+                    (
+                        "Unable to resolve %(item_name)s %(qname)s in "
+                        "%(file)s. (via %(parent)s)"
+                    ) % {
+                        'item_name': exc.item_name,
+                        'item_name': exc.item_name,
+                        'qname': exc.qname,
+                        'file': exc.location,
+                        'parent': obj.qname,
+                    })
 
         _resolve_dict(self._attribute_groups)
         _resolve_dict(self._attributes)
@@ -305,10 +397,6 @@ class SchemaDocument(object):
         _resolve_dict(self._groups)
         _resolve_dict(self._types)
 
-        for element in self._elm_instances:
-            element.resolve()
-        self._elm_instances = []
-
     def register_import(self, namespace, schema):
         schemas = self._imports.setdefault(namespace, [])
         schemas.append(schema)
@@ -350,23 +438,43 @@ class SchemaDocument(object):
         self._attribute_groups[name] = value
 
     def get_type(self, qname):
-        """Return a xsd.Type object from this schema"""
+        """Return a xsd.Type object from this schema
+
+        :rtype: zeep.xsd.ComplexType or zeep.xsd.AnySimpleType
+
+        """
         return self._get_instance(qname, self._types, 'type')
 
     def get_element(self, qname):
-        """Return a xsd.Element object from this schema"""
+        """Return a xsd.Element object from this schema
+
+        :rtype: zeep.xsd.Element
+
+        """
         return self._get_instance(qname, self._elements, 'element')
 
     def get_group(self, qname):
-        """Return a xsd.Group object from this schema"""
+        """Return a xsd.Group object from this schema.
+
+        :rtype: zeep.xsd.Group
+
+        """
         return self._get_instance(qname, self._groups, 'group')
 
     def get_attribute(self, qname):
-        """Return a xsd.Attribute object from this schema"""
+        """Return a xsd.Attribute object from this schema
+
+        :rtype: zeep.xsd.Attribute
+
+        """
         return self._get_instance(qname, self._attributes, 'attribute')
 
     def get_attribute_group(self, qname):
-        """Return a xsd.AttributeGroup object from this schema"""
+        """Return a xsd.AttributeGroup object from this schema
+
+        :rtype: zeep.xsd.AttributeGroup
+
+        """
         return self._get_instance(qname, self._attribute_groups, 'attributeGroup')
 
     def _get_instance(self, qname, items, item_name):
@@ -377,10 +485,13 @@ class SchemaDocument(object):
             raise exceptions.LookupError((
                 "No %(item_name)s '%(localname)s' in namespace %(namespace)s. " +
                 "Available %(item_name_plural)s are: %(known_items)s"
-            ) % {
-                'item_name': item_name,
-                'item_name_plural': item_name + 's',
-                'localname': qname.localname,
-                'namespace': qname.namespace,
-                'known_items': known_items or ' - '
-            })
+                ) % {
+                    'item_name': item_name,
+                    'item_name_plural': item_name + 's',
+                    'localname': qname.localname,
+                    'namespace': qname.namespace,
+                    'known_items': known_items or ' - '
+                },
+                qname=qname,
+                item_name=item_name,
+                location=self._location)
diff --git a/src/zeep/xsd/types/any.py b/src/zeep/xsd/types/any.py
index a667b61..c5e994d 100644
--- a/src/zeep/xsd/types/any.py
+++ b/src/zeep/xsd/types/any.py
@@ -12,6 +12,11 @@ __all__ = ['AnyType']
 
 class AnyType(Type):
     _default_qname = xsd_ns('anyType')
+    _attributes_unwrapped = []
+    _element = None
+
+    def __call__(self, value=None):
+        return value or ''
 
     def render(self, parent, value, xsd_type=None, render_path=None):
         if isinstance(value, AnyObject):
@@ -27,7 +32,22 @@ class AnyType(Type):
             parent.text = self.xmlvalue(value)
 
     def parse_xmlelement(self, xmlelement, schema=None, allow_none=True,
-                         context=None):
+                         context=None, schema_type=None):
+        """Consume matching xmlelements and call parse() on each
+
+        :param xmlelement: XML element objects
+        :type xmlelement: lxml.etree._Element
+        :param schema: The parent XML schema
+        :type schema: zeep.xsd.Schema
+        :param allow_none: Allow none
+        :type allow_none: bool
+        :param context: Optional parsing context (for inline schemas)
+        :type context: zeep.xsd.context.XmlParserContext
+        :param schema_type: The original type (not overriden via xsi:type)
+        :type schema_type: zeep.xsd.types.base.Type
+        :rtype: dict or None
+
+        """
         xsi_type = qname_attr(xmlelement, xsi_ns('type'))
         xsi_nil = xmlelement.get(xsi_ns('nil'))
         children = list(xmlelement.getchildren())
@@ -42,8 +62,14 @@ class AnyType(Type):
             xsd_type = schema.get_type(xsi_type, fail_silently=True)
 
             # If we were unable to resolve a type for the xsi:type (due to
-            # buggy soap servers) then we just return the lxml element.
+            # buggy soap servers) then we just return the text or lxml element.
             if not xsd_type:
+                logger.debug(
+                    "Unable to resolve type for %r, returning raw data",
+                    xsi_type.text)
+
+                if xmlelement.text:
+                    return xmlelement.text
                 return children
 
             # If the xsd_type is xsd:anyType then we will recurs so ignore
@@ -72,3 +98,6 @@ class AnyType(Type):
 
     def pythonvalue(self, value, schema=None):
         return value
+
+    def signature(self, schema=None, standalone=True):
+        return 'xsd:anyType'
diff --git a/src/zeep/xsd/types/base.py b/src/zeep/xsd/types/base.py
index d67cb85..f8a2233 100644
--- a/src/zeep/xsd/types/base.py
+++ b/src/zeep/xsd/types/base.py
@@ -1,3 +1,8 @@
+from zeep.xsd.utils import create_prefixed_name
+
+__all__ = ['Type']
+
+
 class Type(object):
 
     def __init__(self, qname=None, is_global=False):
@@ -6,9 +11,16 @@ class Type(object):
         self._resolved = False
         self.is_global = is_global
 
+    def get_prefixed_name(self, schema):
+        return create_prefixed_name(self.qname, schema)
+
     def accept(self, value):
         raise NotImplementedError
 
+    @property
+    def accepted_types(self):
+        return tuple()
+
     def validate(self, value, required=False):
         return
 
@@ -23,7 +35,7 @@ class Type(object):
         return {}
 
     def parse_xmlelement(self, xmlelement, schema=None, allow_none=True,
-                         context=None):
+                         context=None, schema_type=None):
         raise NotImplementedError(
             '%s.parse_xmlelement() is not implemented' % self.__class__.__name__)
 
@@ -51,61 +63,5 @@ class Type(object):
         return []
 
     @classmethod
-    def signature(cls, depth=()):
+    def signature(cls, schema=None, standalone=True):
         return ''
-
-
-class UnresolvedType(Type):
-    def __init__(self, qname, schema):
-        self.qname = qname
-        assert self.qname.text != 'None'
-        self.schema = schema
-
-    def __repr__(self):
-        return '<%s(qname=%r)>' % (self.__class__.__name__, self.qname)
-
-    def render(self, parent, value, xsd_type=None, render_path=None):
-        raise RuntimeError(
-            "Unable to render unresolved type %s. This is probably a bug." % (
-                self.qname))
-
-    def resolve(self):
-        retval = self.schema.get_type(self.qname)
-        return retval.resolve()
-
-
-class UnresolvedCustomType(Type):
-
-    def __init__(self, qname, base_type, schema):
-        assert qname is not None
-        self.qname = qname
-        self.name = str(qname.localname)
-        self.schema = schema
-        self.base_type = base_type
-
-    def __repr__(self):
-        return '<%s(qname=%r, base_type=%r)>' % (
-            self.__class__.__name__, self.qname.text, self.base_type)
-
-    def resolve(self):
-        base = self.base_type
-        base = base.resolve()
-
-        cls_attributes = {
-            '__module__': 'zeep.xsd.dynamic_types',
-        }
-
-        from zeep.xsd.types.collection import UnionType  # FIXME
-        from zeep.xsd.types.simple import AnySimpleType  # FIXME
-
-        if issubclass(base.__class__, UnionType):
-            xsd_type = type(self.name, (base.__class__,), cls_attributes)
-            return xsd_type(base.item_types)
-
-        elif issubclass(base.__class__, AnySimpleType):
-            xsd_type = type(self.name, (base.__class__,), cls_attributes)
-            return xsd_type(self.qname)
-
-        else:
-            xsd_type = type(self.name, (base.base_class,), cls_attributes)
-            return xsd_type(self.qname)
diff --git a/src/zeep/xsd/types/builtins.py b/src/zeep/xsd/types/builtins.py
index 49b8c3e..0bbe5d9 100644
--- a/src/zeep/xsd/types/builtins.py
+++ b/src/zeep/xsd/types/builtins.py
@@ -17,6 +17,11 @@ class ParseError(ValueError):
     pass
 
 
+class BuiltinType(object):
+    def __init__(self, qname=None, is_global=False):
+        super(BuiltinType, self).__init__(qname, is_global=True)
+
+
 def check_no_collection(func):
     def _wrapper(self, value):
         if isinstance(value, (list, dict, set)):
@@ -30,7 +35,7 @@ def check_no_collection(func):
 
 ##
 # Primitive types
-class String(AnySimpleType):
+class String(BuiltinType, AnySimpleType):
     _default_qname = xsd_ns('string')
     accepted_types = six.string_types
 
@@ -44,13 +49,13 @@ class String(AnySimpleType):
         return value
 
 
-class Boolean(AnySimpleType):
+class Boolean(BuiltinType, AnySimpleType):
     _default_qname = xsd_ns('boolean')
     accepted_types = (bool,)
 
     @check_no_collection
     def xmlvalue(self, value):
-        return 'true' if value else 'false'
+        return 'true' if value and value not in ('false', '0') else 'false'
 
     def pythonvalue(self, value):
         """Return True if the 'true' or '1'. 'false' and '0' are legal false
@@ -60,7 +65,7 @@ class Boolean(AnySimpleType):
         return value in ('true', '1')
 
 
-class Decimal(AnySimpleType):
+class Decimal(BuiltinType, AnySimpleType):
     _default_qname = xsd_ns('decimal')
     accepted_types = (_Decimal, float) + six.string_types
 
@@ -72,7 +77,7 @@ class Decimal(AnySimpleType):
         return _Decimal(value)
 
 
-class Float(AnySimpleType):
+class Float(BuiltinType, AnySimpleType):
     _default_qname = xsd_ns('float')
     accepted_types = (float, _Decimal) + six.string_types
 
@@ -83,7 +88,7 @@ class Float(AnySimpleType):
         return float(value)
 
 
-class Double(AnySimpleType):
+class Double(BuiltinType, AnySimpleType):
     _default_qname = xsd_ns('double')
     accepted_types = (_Decimal, float) + six.string_types
 
@@ -95,7 +100,7 @@ class Double(AnySimpleType):
         return float(value)
 
 
-class Duration(AnySimpleType):
+class Duration(BuiltinType, AnySimpleType):
     _default_qname = xsd_ns('duration')
     accepted_types = (isodate.duration.Duration,) + six.string_types
 
@@ -107,7 +112,7 @@ class Duration(AnySimpleType):
         return isodate.parse_duration(value)
 
 
-class DateTime(AnySimpleType):
+class DateTime(BuiltinType, AnySimpleType):
     _default_qname = xsd_ns('dateTime')
     accepted_types = (datetime.datetime,) + six.string_types
 
@@ -131,7 +136,7 @@ class DateTime(AnySimpleType):
         return isodate.parse_datetime(value)
 
 
-class Time(AnySimpleType):
+class Time(BuiltinType, AnySimpleType):
     _default_qname = xsd_ns('time')
     accepted_types = (datetime.time,) + six.string_types
 
@@ -145,7 +150,7 @@ class Time(AnySimpleType):
         return isodate.parse_time(value)
 
 
-class Date(AnySimpleType):
+class Date(BuiltinType, AnySimpleType):
     _default_qname = xsd_ns('date')
     accepted_types = (datetime.date,) + six.string_types
 
@@ -159,7 +164,7 @@ class Date(AnySimpleType):
         return isodate.parse_date(value)
 
 
-class gYearMonth(AnySimpleType):
+class gYearMonth(BuiltinType, AnySimpleType):
     """gYearMonth represents a specific gregorian month in a specific gregorian
     year.
 
@@ -186,7 +191,7 @@ class gYearMonth(AnySimpleType):
             _parse_timezone(group['timezone']))
 
 
-class gYear(AnySimpleType):
+class gYear(BuiltinType, AnySimpleType):
     """gYear represents a gregorian calendar year.
 
     Lexical representation: CCYY
@@ -209,7 +214,7 @@ class gYear(AnySimpleType):
         return (int(group['year']), _parse_timezone(group['timezone']))
 
 
-class gMonthDay(AnySimpleType):
+class gMonthDay(BuiltinType, AnySimpleType):
     """gMonthDay is a gregorian date that recurs, specifically a day of the
     year such as the third of May.
 
@@ -237,7 +242,7 @@ class gMonthDay(AnySimpleType):
             _parse_timezone(group['timezone']))
 
 
-class gDay(AnySimpleType):
+class gDay(BuiltinType, AnySimpleType):
     """gDay is a gregorian day that recurs, specifically a day of the month
     such as the 5th of the month
 
@@ -261,7 +266,7 @@ class gDay(AnySimpleType):
         return (int(group['day']), _parse_timezone(group['timezone']))
 
 
-class gMonth(AnySimpleType):
+class gMonth(BuiltinType, AnySimpleType):
     """gMonth is a gregorian month that recurs every year.
 
     Lexical representation: --MM
@@ -284,7 +289,7 @@ class gMonth(AnySimpleType):
         return (int(group['month']), _parse_timezone(group['timezone']))
 
 
-class HexBinary(AnySimpleType):
+class HexBinary(BuiltinType, AnySimpleType):
     accepted_types = six.string_types
     _default_qname = xsd_ns('hexBinary')
 
@@ -296,7 +301,7 @@ class HexBinary(AnySimpleType):
         return value
 
 
-class Base64Binary(AnySimpleType):
+class Base64Binary(BuiltinType, AnySimpleType):
     accepted_types = six.string_types
     _default_qname = xsd_ns('base64Binary')
 
@@ -308,7 +313,7 @@ class Base64Binary(AnySimpleType):
         return base64.b64decode(value)
 
 
-class AnyURI(AnySimpleType):
+class AnyURI(BuiltinType, AnySimpleType):
     accepted_types = six.string_types
     _default_qname = xsd_ns('anyURI')
 
@@ -320,7 +325,7 @@ class AnyURI(AnySimpleType):
         return value
 
 
-class QName(AnySimpleType):
+class QName(BuiltinType, AnySimpleType):
     accepted_types = six.string_types
     _default_qname = xsd_ns('QName')
 
@@ -332,7 +337,7 @@ class QName(AnySimpleType):
         return value
 
 
-class Notation(AnySimpleType):
+class Notation(BuiltinType, AnySimpleType):
     accepted_types = six.string_types
     _default_qname = xsd_ns('NOTATION')
 
@@ -390,6 +395,7 @@ class Entities(Entity):
 
 class Integer(Decimal):
     _default_qname = xsd_ns('integer')
+    accepted_types = (int, float) + six.string_types
 
     def xmlvalue(self, value):
         return str(value)
@@ -484,58 +490,60 @@ def _unparse_timezone(tzinfo):
     return '-%02d:%02d' % (abs(hours), minutes)
 
 
+_types = [
+    # Primitive
+    String,
+    Boolean,
+    Decimal,
+    Float,
+    Double,
+    Duration,
+    DateTime,
+    Time,
+    Date,
+    gYearMonth,
+    gYear,
+    gMonthDay,
+    gDay,
+    gMonth,
+    HexBinary,
+    Base64Binary,
+    AnyURI,
+    QName,
+    Notation,
+
+    # Derived
+    NormalizedString,
+    Token,
+    Language,
+    NmToken,
+    NmTokens,
+    Name,
+    NCName,
+    ID,
+    IDREF,
+    IDREFS,
+    Entity,
+    Entities,
+    Integer,
+    NonPositiveInteger,  # noqa
+    NegativeInteger,
+    Long,
+    Int,
+    Short,
+    Byte,
+    NonNegativeInteger,  # noqa
+    UnsignedByte,
+    UnsignedInt,
+    UnsignedLong,
+    UnsignedShort,
+    PositiveInteger,
+
+    # Other
+    AnyType,
+    AnySimpleType,
+]
+
 default_types = {
-    cls._default_qname: cls() for cls in [
-        # Primitive
-        String,
-        Boolean,
-        Decimal,
-        Float,
-        Double,
-        Duration,
-        DateTime,
-        Time,
-        Date,
-        gYearMonth,
-        gYear,
-        gMonthDay,
-        gDay,
-        gMonth,
-        HexBinary,
-        Base64Binary,
-        AnyURI,
-        QName,
-        Notation,
-
-        # Derived
-        NormalizedString,
-        Token,
-        Language,
-        NmToken,
-        NmTokens,
-        Name,
-        NCName,
-        ID,
-        IDREF,
-        IDREFS,
-        Entity,
-        Entities,
-        Integer,
-        NonPositiveInteger,  # noqa
-        NegativeInteger,
-        Long,
-        Int,
-        Short,
-        Byte,
-        NonNegativeInteger,  # noqa
-        UnsignedByte,
-        UnsignedInt,
-        UnsignedLong,
-        UnsignedShort,
-        PositiveInteger,
-
-        # Other
-        AnyType,
-        AnySimpleType,
-    ]
+    cls._default_qname: cls(is_global=True) for cls in _types
 }
diff --git a/src/zeep/xsd/types/collection.py b/src/zeep/xsd/types/collection.py
index f18b14c..5645f7d 100644
--- a/src/zeep/xsd/types/collection.py
+++ b/src/zeep/xsd/types/collection.py
@@ -32,8 +32,8 @@ class ListType(AnySimpleType):
         item_type = self.item_type
         return [item_type.pythonvalue(v) for v in value.split()]
 
-    def signature(self, depth=()):
-        return self.item_type.signature(depth) + '[]'
+    def signature(self, schema=None, standalone=True):
+        return self.item_type.signature(schema) + '[]'
 
 
 class UnionType(AnySimpleType):
@@ -52,11 +52,11 @@ class UnionType(AnySimpleType):
             self.item_class = base_class
         return self
 
-    def signature(self, depth=()):
+    def signature(self, schema=None, standalone=True):
         return ''
 
     def parse_xmlelement(self, xmlelement, schema=None, allow_none=True,
-                         context=None):
+                         context=None, schema_type=None):
         if self.item_class:
             return self.item_class().parse_xmlelement(
                 xmlelement, schema, allow_none, context)
diff --git a/src/zeep/xsd/types/complex.py b/src/zeep/xsd/types/complex.py
index c1b6925..7e65a70 100644
--- a/src/zeep/xsd/types/complex.py
+++ b/src/zeep/xsd/types/complex.py
@@ -6,14 +6,14 @@ from itertools import chain
 from cached_property import threaded_cached_property
 
 from zeep.exceptions import UnexpectedElementError, XMLParseError
-from zeep.xsd.const import xsi_ns, SkipValue, NotSet
+from zeep.xsd.const import NotSet, SkipValue, xsi_ns
 from zeep.xsd.elements import (
     Any, AnyAttribute, AttributeGroup, Choice, Element, Group, Sequence)
 from zeep.xsd.elements.indicators import OrderIndicator
 from zeep.xsd.types.any import AnyType
 from zeep.xsd.types.simple import AnySimpleType
 from zeep.xsd.utils import NamePrefixGenerator
-from zeep.xsd.valueobjects import CompoundValue
+from zeep.xsd.valueobjects import CompoundValue, ArrayValue
 
 logger = logging.getLogger(__name__)
 
@@ -33,14 +33,24 @@ class ComplexType(AnyType):
         self._attributes = attributes or []
         self._restriction = restriction
         self._extension = extension
+        self._extension_types = tuple()
         super(ComplexType, self).__init__(qname=qname, is_global=is_global)
 
     def __call__(self, *args, **kwargs):
+        if self._array_type:
+            return self._array_class(*args, **kwargs)
         return self._value_class(*args, **kwargs)
 
     @property
     def accepted_types(self):
-        return (self._value_class,)
+        return (self._value_class,) + self._extension_types
+
+    @threaded_cached_property
+    def _array_class(self):
+        assert self._array_type
+        return type(
+            self.__class__.__name__, (ArrayValue,),
+            {'_xsd_type': self, '__module__': 'zeep.objects'})
 
     @threaded_cached_property
     def _value_class(self):
@@ -94,25 +104,43 @@ class ComplexType(AnyType):
         generator = NamePrefixGenerator()
 
         # Handle wsdl:arrayType objects
-        attrs = {attr.qname.text: attr for attr in self._attributes if attr.qname}
-        array_type = attrs.get('{http://schemas.xmlsoap.org/soap/encoding/}arrayType')
-        if array_type:
+        if self._array_type:
             name = generator.get_name()
             if isinstance(self._element, Group):
-                return [(name, Sequence([
-                    Any(max_occurs='unbounded', restrict=array_type.array_type)
+                result = [(name, Sequence([
+                    Any(max_occurs='unbounded', restrict=self._array_type.array_type)
                 ]))]
             else:
-                return [(name, self._element)]
-
-        # _element is one of All, Choice, Group, Sequence
-        if self._element:
-            result.append((generator.get_name(), self._element))
+                result = [(name, self._element)]
+        else:
+            # _element is one of All, Choice, Group, Sequence
+            if self._element:
+                result.append((generator.get_name(), self._element))
         return result
 
-    def parse_xmlelement(self, xmlelement, schema, allow_none=True,
-                         context=None):
-        """Consume matching xmlelements and call parse() on each"""
+    @property
+    def _array_type(self):
+        attrs = {attr.qname.text: attr for attr in self._attributes if attr.qname}
+        array_type = attrs.get('{http://schemas.xmlsoap.org/soap/encoding/}arrayType')
+        return array_type
+
+    def parse_xmlelement(self, xmlelement, schema=None, allow_none=True,
+                         context=None, schema_type=None):
+        """Consume matching xmlelements and call parse() on each
+
+        :param xmlelement: XML element objects
+        :type xmlelement: lxml.etree._Element
+        :param schema: The parent XML schema
+        :type schema: zeep.xsd.Schema
+        :param allow_none: Allow none
+        :type allow_none: bool
+        :param context: Optional parsing context (for inline schemas)
+        :type context: zeep.xsd.context.XmlParserContext
+        :param schema_type: The original type (not overriden via xsi:type)
+        :type schema_type: zeep.xsd.types.base.Type
+        :rtype: dict or None
+
+        """
         # If this is an empty complexType (<xsd:complexType name="x"/>)
         if not self.attributes and not self.elements:
             return None
@@ -134,6 +162,7 @@ class ComplexType(AnyType):
 
             # Parse elements. These are always indicator elements (all, choice,
             # group, sequence)
+            assert len(self.elements_nested) < 2
             for name, element in self.elements_nested:
                 try:
                     result = element.parse_xmlelements(
@@ -145,7 +174,10 @@ class ComplexType(AnyType):
 
             # Check if all children are consumed (parsed)
             if elements:
-                raise XMLParseError("Unexpected element %r" % elements[0].tag)
+                if schema.strict:
+                    raise XMLParseError("Unexpected element %r" % elements[0].tag)
+                else:
+                    init_kwargs['_raw_elements'] = elements
 
         # Parse attributes
         if attributes:
@@ -158,12 +190,21 @@ class ComplexType(AnyType):
                 else:
                     init_kwargs[name] = attribute.parse(attributes)
 
-        return self(**init_kwargs)
+        value = self._value_class(**init_kwargs)
+        schema_type = schema_type or self
+        if schema_type and getattr(schema_type, '_array_type', None):
+            value = schema_type._array_class.from_value_object(value)
+        return value
 
     def render(self, parent, value, xsd_type=None, render_path=None):
         """Serialize the given value lxml.Element subelements on the parent
         element.
 
+        :type parent: lxml.etree._Element
+        :type value: Union[list, dict, zeep.xsd.valueobjects.CompoundValue]
+        :type xsd_type: zeep.xsd.types.base.Type
+        :param render_path: list
+
         """
         if not render_path:
             render_path = [self.name]
@@ -171,12 +212,24 @@ class ComplexType(AnyType):
         if not self.elements_nested and not self.attributes:
             return
 
+        if isinstance(value, ArrayValue):
+            value = value.as_value_object()
+
         # Render attributes
         for name, attribute in self.attributes:
             attr_value = value[name] if name in value else NotSet
             child_path = render_path + [name]
             attribute.render(parent, attr_value, child_path)
 
+        if (
+            len(self.elements_nested) == 1
+            and isinstance(value, self.accepted_types)
+            and not isinstance(value, (list, dict, CompoundValue))
+        ):
+            element = self.elements_nested[0][1]
+            element.type.render(parent, value, None, child_path)
+            return
+
         # Render sub elements
         for name, element in self.elements_nested:
             if isinstance(element, Element) or element.accepts_multiple:
@@ -186,6 +239,7 @@ class ComplexType(AnyType):
                 element_value = value
                 child_path = list(render_path)
 
+            # We want to explicitly skip this sub-element
             if element_value is SkipValue:
                 continue
 
@@ -201,6 +255,19 @@ class ComplexType(AnyType):
                 parent.set(xsi_ns('type'), xsd_type.qname)
 
     def parse_kwargs(self, kwargs, name, available_kwargs):
+        """Parse the kwargs for this type and return the accepted data as
+        a dict.
+
+        :param kwargs: The kwargs
+        :type kwargs: dict
+        :param name: The name as which this type is registered in the parent
+        :type name: str
+        :param available_kwargs: The kwargs keys which are still available,
+         modified in place
+        :type available_kwargs: set
+        :rtype: dict
+
+        """
         value = None
         name = name or self.name
 
@@ -213,28 +280,27 @@ class ComplexType(AnyType):
         return {}
 
     def _create_object(self, value, name):
-        """Return the value as a CompoundValue object"""
+        """Return the value as a CompoundValue object
+
+        :type value: str
+        :type value: list, dict, CompoundValue
+
+        """
         if value is None:
             return None
 
-        if isinstance(value, list):
+        if isinstance(value, list) and not self._array_type:
             return [self._create_object(val, name) for val in value]
 
-        if isinstance(value, CompoundValue):
+        if isinstance(value, CompoundValue) or value is SkipValue:
             return value
 
         if isinstance(value, dict):
             return self(**value)
 
-        # Check if the valueclass only expects one value, in that case
-        # we can try to automatically create an object for it.
-        if len(self.attributes) + len(self.elements) == 1:
-            return self(value)
-
-        raise ValueError((
-            "Error while create XML for complexType '%s': "
-            "Expected instance of type %s, received %r instead."
-        ) % (self.qname or name, self._value_class, type(value)))
+        # Try to automatically create an object. This might fail if there
+        # are multiple required arguments.
+        return self(value)
 
     def resolve(self):
         """Resolve all sub elements and types"""
@@ -242,9 +308,6 @@ class ComplexType(AnyType):
             return self._resolved
         self._resolved = self
 
-        if self._element:
-            self._element = self._element.resolve()
-
         resolved = []
         for attribute in self._attributes:
             value = attribute.resolve()
@@ -258,18 +321,17 @@ class ComplexType(AnyType):
         if self._extension:
             self._extension = self._extension.resolve()
             self._resolved = self.extend(self._extension)
-            return self._resolved
-
         elif self._restriction:
             self._restriction = self._restriction.resolve()
             self._resolved = self.restrict(self._restriction)
-            return self._resolved
 
-        else:
-            return self._resolved
+        if self._element:
+            self._element = self._element.resolve()
+
+        return self._resolved
 
     def extend(self, base):
-        """Create a new complextype instance which is the current type
+        """Create a new ComplexType instance which is the current type
         extending the given base type.
 
         Used for handling xsd:extension tags
@@ -277,6 +339,9 @@ class ComplexType(AnyType):
         TODO: Needs a rewrite where the child containers are responsible for
         the extend functionality.
 
+        :type base: zeep.xsd.types.base.Type
+        :rtype base: zeep.xsd.types.base.Type
+
         """
         if isinstance(base, ComplexType):
             base_attributes = base._attributes_unwrapped
@@ -301,6 +366,9 @@ class ComplexType(AnyType):
         # container a placeholder element).
         element = []
         if self._element and base_element:
+            self._element = self._element.resolve()
+            base_element = base_element.resolve()
+
             element = self._element.clone(self._element.name)
             if isinstance(base_element, OrderIndicator):
                 if isinstance(self._element, Choice):
@@ -323,7 +391,10 @@ class ComplexType(AnyType):
         new = self.__class__(
             element=element,
             attributes=attributes,
-            qname=self.qname)
+            qname=self.qname,
+            is_global=self.is_global)
+
+        new._extension_types = base.accepted_types
         return new
 
     def restrict(self, base):
@@ -332,6 +403,10 @@ class ComplexType(AnyType):
 
         Used for handling xsd:restriction
 
+        :type base: zeep.xsd.types.base.Type
+        :rtype base: zeep.xsd.types.base.Type
+
+
         """
         attributes = list(
             chain(base._attributes_unwrapped, self._attributes_unwrapped))
@@ -344,33 +419,30 @@ class ComplexType(AnyType):
                     new_attributes['##any'] = attr
                 else:
                     new_attributes[attr.qname.text] = attr
-            attributes = new_attributes.values()
+            attributes = list(new_attributes.values())
+
+        if base._element:
+            base._element.resolve()
 
         new = self.__class__(
             element=self._element or base._element,
             attributes=attributes,
-            qname=self.qname)
+            qname=self.qname,
+            is_global=self.is_global)
         return new.resolve()
 
-    def signature(self, depth=()):
-        if len(depth) > 0 and self.is_global:
-            return self.name
-
+    def signature(self, schema=None, standalone=True):
         parts = []
-        depth += (self.name,)
         for name, element in self.elements_nested:
-            # http://schemas.xmlsoap.org/soap/encoding/ contains cyclic type
-            if isinstance(element, Element) and element.type == self:
-                continue
-
-            part = element.signature(depth)
+            part = element.signature(schema, standalone=False)
             parts.append(part)
 
         for name, attribute in self.attributes:
-            part = '%s: %s' % (name, attribute.signature(depth))
+            part = '%s: %s' % (name, attribute.signature(schema, standalone=False))
             parts.append(part)
 
         value = ', '.join(parts)
-        if len(depth) > 1:
-            value = '{%s}' % value
-        return value
+        if standalone:
+            return '%s(%s)' % (self.get_prefixed_name(schema), value)
+        else:
+            return value
diff --git a/src/zeep/xsd/types/simple.py b/src/zeep/xsd/types/simple.py
index 989d292..4c5440a 100644
--- a/src/zeep/xsd/types/simple.py
+++ b/src/zeep/xsd/types/simple.py
@@ -4,7 +4,7 @@ import six
 from lxml import etree
 
 from zeep.exceptions import ValidationError
-from zeep.xsd.const import NS_XSD, xsd_ns
+from zeep.xsd.const import xsd_ns
 from zeep.xsd.types.any import AnyType
 
 logger = logging.getLogger(__name__)
@@ -54,7 +54,7 @@ class AnySimpleType(AnyType):
         return '%s(value)' % (self.__class__.__name__)
 
     def parse_xmlelement(self, xmlelement, schema=None, allow_none=True,
-                         context=None):
+                         context=None, schema_type=None):
         if xmlelement.text is None:
             return
         try:
@@ -70,10 +70,8 @@ class AnySimpleType(AnyType):
     def render(self, parent, value, xsd_type=None, render_path=None):
         parent.text = self.xmlvalue(value)
 
-    def signature(self, depth=()):
-        if self.qname.namespace == NS_XSD:
-            return 'xsd:%s' % self.name
-        return self.name
+    def signature(self, schema=None, standalone=True):
+        return self.get_prefixed_name(schema)
 
     def validate(self, value, required=False):
         if required and value is None:
diff --git a/src/zeep/xsd/types/unresolved.py b/src/zeep/xsd/types/unresolved.py
new file mode 100644
index 0000000..e15a03e
--- /dev/null
+++ b/src/zeep/xsd/types/unresolved.py
@@ -0,0 +1,56 @@
+from zeep.xsd.types.base import Type
+from zeep.xsd.types.collection import UnionType  # FIXME
+from zeep.xsd.types.simple import AnySimpleType  # FIXME
+
+
+class UnresolvedType(Type):
+    def __init__(self, qname, schema):
+        self.qname = qname
+        assert self.qname.text != 'None'
+        self.schema = schema
+
+    def __repr__(self):
+        return '<%s(qname=%r)>' % (self.__class__.__name__, self.qname.text)
+
+    def render(self, parent, value, xsd_type=None, render_path=None):
+        raise RuntimeError(
+            "Unable to render unresolved type %s. This is probably a bug." % (
+                self.qname))
+
+    def resolve(self):
+        retval = self.schema.get_type(self.qname)
+        return retval.resolve()
+
+
+class UnresolvedCustomType(Type):
+
+    def __init__(self, qname, base_type, schema):
+        assert qname is not None
+        self.qname = qname
+        self.name = str(qname.localname)
+        self.schema = schema
+        self.base_type = base_type
+
+    def __repr__(self):
+        return '<%s(qname=%r, base_type=%r)>' % (
+            self.__class__.__name__, self.qname.text, self.base_type)
+
+    def resolve(self):
+        base = self.base_type
+        base = base.resolve()
+
+        cls_attributes = {
+            '__module__': 'zeep.xsd.dynamic_types',
+        }
+
+        if issubclass(base.__class__, UnionType):
+            xsd_type = type(self.name, (base.__class__,), cls_attributes)
+            return xsd_type(base.item_types)
+
+        elif issubclass(base.__class__, AnySimpleType):
+            xsd_type = type(self.name, (base.__class__,), cls_attributes)
+            return xsd_type(self.qname)
+
+        else:
+            xsd_type = type(self.name, (base.base_class,), cls_attributes)
+            return xsd_type(self.qname)
diff --git a/src/zeep/xsd/utils.py b/src/zeep/xsd/utils.py
index 2b5a3cc..e60c8f6 100644
--- a/src/zeep/xsd/utils.py
+++ b/src/zeep/xsd/utils.py
@@ -1,10 +1,6 @@
-from defusedxml.lxml import fromstring
-from lxml import etree
-
 from six.moves import range
-from six.moves.urllib.parse import urlparse
-from zeep.exceptions import XMLSyntaxError
-from zeep.parser import absolute_location
+
+from zeep import ns
 
 
 class NamePrefixGenerator(object):
@@ -31,34 +27,6 @@ class UniqueNameGenerator(object):
             return name
 
 
-class ImportResolver(etree.Resolver):
-    """Custom lxml resolve to use the transport object"""
-    def __init__(self, transport):
-        self.transport = transport
-
-    def resolve(self, url, pubid, context):
-        if urlparse(url).scheme in ('http', 'https'):
-            content = self.transport.load(url)
-            return self.resolve_string(content, context)
-
-
-def parse_xml(content, transport, base_url=None):
-    parser = etree.XMLParser(remove_comments=True, resolve_entities=False)
-    parser.resolvers.add(ImportResolver(transport))
-    try:
-        return fromstring(content, parser=parser, base_url=base_url)
-    except etree.XMLSyntaxError as exc:
-        raise XMLSyntaxError("Invalid XML content received (%s)" % exc.message)
-
-
-def load_external(url, transport, base_url=None):
-    if base_url:
-        url = absolute_location(url, base_url)
-
-    response = transport.load(url)
-    return parse_xml(response, transport, base_url)
-
-
 def max_occurs_iter(max_occurs, items=None):
     assert max_occurs is not None
     generator = range(0, max_occurs if max_occurs != 'unbounded' else 2**31-1)
@@ -69,3 +37,27 @@ def max_occurs_iter(max_occurs, items=None):
     else:
         for i in generator:
             yield i
+
+
+def create_prefixed_name(qname, schema):
+    """Convert a QName to a xsd:name ('ns1:myType').
+
+    :type qname: lxml.etree.QName
+    :type schema: zeep.xsd.schema.Schema
+    :rtype: str
+
+    """
+    if not qname:
+        return
+
+    if schema and qname.namespace:
+        prefix = schema.get_shorthand_for_ns(qname.namespace)
+        if prefix:
+            return '%s:%s' % (prefix, qname.localname)
+    elif qname.namespace in ns.NAMESPACE_TO_PREFIX:
+        prefix = ns.NAMESPACE_TO_PREFIX[qname.namespace]
+        return '%s:%s' % (prefix, qname.localname)
+
+    if qname.namespace:
+        return qname.text
+    return qname.localname
diff --git a/src/zeep/xsd/valueobjects.py b/src/zeep/xsd/valueobjects.py
index c2cf9c5..7ba86d8 100644
--- a/src/zeep/xsd/valueobjects.py
+++ b/src/zeep/xsd/valueobjects.py
@@ -33,11 +33,46 @@ class AnyObject(object):
         return self.xsd_obj
 
 
+def _unpickle_compound_value(name, values):
+    """Helper function to recreate pickled CompoundValue.
+
+    See CompoundValue.__reduce__
+
+    """
+    cls = type(name, (CompoundValue,), {
+        '_xsd_type': None, '__module__': 'zeep.objects'
+    })
+    obj = cls()
+    obj.__values__ = values
+    return obj
+
+
+class ArrayValue(list):
+    def __init__(self, items):
+        super(ArrayValue, self).__init__(items)
+
+    def as_value_object(self):
+        anon_type = type(
+            self.__class__.__name__, (CompoundValue,),
+            {'_xsd_type': self._xsd_type, '__module__': 'zeep.objects'})
+        return anon_type(list(self))
+
+    @classmethod
+    def from_value_object(cls, obj):
+        items = next(iter(obj.__values__.values()))
+        return cls(items or [])
+
+
 class CompoundValue(object):
+    """Represents a data object for a specific xsd:complexType."""
 
     def __init__(self, *args, **kwargs):
         values = OrderedDict()
 
+        # Can be done after unpickle
+        if self._xsd_type is None:
+            return
+
         # Set default values
         for container_name, container in self._xsd_type.elements_nested:
             elm_values = container.default_value
@@ -56,6 +91,9 @@ class CompoundValue(object):
             values[key] = value
         self.__values__ = values
 
+    def __reduce__(self):
+        return (_unpickle_compound_value, (self.__class__.__name__, self.__values__,))
+
     def __contains__(self, key):
         return self.__values__.__contains__(key)
 
@@ -107,6 +145,9 @@ class CompoundValue(object):
                 setattr(new, attr, value)
         return new
 
+    def __json__(self):
+        return self.__values__
+
 
 def _process_signature(xsd_type, args, kwargs):
     """Return a dict with the args/kwargs mapped to the field name.
@@ -169,13 +210,19 @@ def _process_signature(xsd_type, args, kwargs):
                 available_kwargs.remove(attribute_name)
                 result[attribute_name] = kwargs[attribute_name]
 
+    # _raw_elements is a special kwarg used for unexpected unparseable xml
+    # elements (e.g. for soap:header or when strict is disabled)
+    if '_raw_elements' in available_kwargs and kwargs['_raw_elements']:
+        result['_raw_elements'] = kwargs['_raw_elements']
+        available_kwargs.remove('_raw_elements')
+
     if available_kwargs:
         raise TypeError((
             "%s() got an unexpected keyword argument %r. " +
-            "Signature: (%s)"
+            "Signature: `%s`"
         ) % (
             xsd_type.qname or 'ComplexType',
             next(iter(available_kwargs)),
-            xsd_type.signature()))
+            xsd_type.signature(standalone=False)))
 
     return result
diff --git a/src/zeep/xsd/visitor.py b/src/zeep/xsd/visitor.py
index 8ed0160..9ba0101 100644
--- a/src/zeep/xsd/visitor.py
+++ b/src/zeep/xsd/visitor.py
@@ -4,14 +4,13 @@ import re
 
 from lxml import etree
 
-from zeep import exceptions
 from zeep.exceptions import XMLParseError
-from zeep.parser import absolute_location
+from zeep.loader import absolute_location, load_external
 from zeep.utils import as_qname, qname_attr
 from zeep.xsd import elements as xsd_elements
 from zeep.xsd import types as xsd_types
 from zeep.xsd.const import xsd_ns
-from zeep.xsd.utils import load_external
+from zeep.xsd.types.unresolved import UnresolvedCustomType, UnresolvedType
 
 logger = logging.getLogger(__name__)
 
@@ -37,12 +36,36 @@ class SchemaVisitor(object):
     """Visitor which processes XSD files and registers global elements and
     types in the given schema.
 
+    :param schema:
+    :type schema: zeep.xsd.schema.Schema
+    :param document:
+    :type document: zeep.xsd.schema.SchemaDocument
+
     """
+
     def __init__(self, schema, document):
         self.document = document
         self.schema = schema
         self._includes = set()
 
+    def register_element(self, qname, instance):
+        self.document.register_element(qname, instance)
+
+    def register_attribute(self, name, instance):
+        self.document.register_attribute(name, instance)
+
+    def register_type(self, qname, instance):
+        self.document.register_type(qname, instance)
+
+    def register_group(self, qname, instance):
+        self.document.register_group(qname, instance)
+
+    def register_attribute_group(self, qname, instance):
+        self.document.register_attribute_group(qname, instance)
+
+    def register_import(self, namespace, document):
+        self.document.register_import(namespace, document)
+
     def process(self, node, parent):
         visit_func = self.visitors.get(node.tag)
         if not visit_func:
@@ -79,7 +102,10 @@ class SchemaVisitor(object):
         return cls(node.tag, ref, self.schema, **kwargs)
 
     def visit_schema(self, node):
-        """
+        """Visit the xsd:schema element and process all the child elements
+
+        Definition::
+
             <schema
               attributeFormDefault = (qualified | unqualified): unqualified
               blockDefault = (#all | List of (extension | restriction | substitution) : ''
@@ -97,6 +123,9 @@ class SchemaVisitor(object):
                  annotation*)*)
             </schema>
 
+        :param node: The XML node
+        :type node: lxml.etree._Element
+
         """
         assert node is not None
 
@@ -104,12 +133,14 @@ class SchemaVisitor(object):
         self.document._element_form = node.get('elementFormDefault', 'unqualified')
         self.document._attribute_form = node.get('attributeFormDefault', 'unqualified')
 
-        parent = node
-        for node in node.iterchildren():
-            self.process(node, parent=parent)
+        for child in node:
+            self.process(child, parent=node)
 
     def visit_import(self, node, parent):
         """
+
+        Definition::
+
             <import
               id = ID
               namespace = anyURI
@@ -117,6 +148,12 @@ class SchemaVisitor(object):
               {any attributes with non-schema Namespace}...>
             Content: (annotation?)
             </import>
+
+        :param node: The XML node
+        :type node: lxml.etree._Element
+        :param parent: The parent XML node
+        :type parent: lxml.etree._Element
+
         """
         schema_node = None
         namespace = node.get('namespace')
@@ -136,7 +173,7 @@ class SchemaVisitor(object):
         document = self.schema._get_schema_document(namespace, location)
         if document:
             logger.debug("Returning existing schema: %r", location)
-            self.document.register_import(namespace, document)
+            self.register_import(namespace, document)
             return document
 
         # Hardcode the mapping between the xml namespace and the xsd for now.
@@ -153,7 +190,10 @@ class SchemaVisitor(object):
             return
 
         # Load the XML
-        schema_node = load_external(location, self.schema._transport)
+        schema_node = load_external(
+            location,
+            self.schema._transport,
+            strict=self.schema.strict)
 
         # Check if the xsd:import namespace matches the targetNamespace. If
         # the xsd:import statement didn't specify a namespace then make sure
@@ -168,17 +208,26 @@ class SchemaVisitor(object):
                 sourceline=node.sourceline)
 
         schema = self.schema.create_new_document(schema_node, location)
-        self.document.register_import(namespace, schema)
+        self.register_import(namespace, schema)
         return schema
 
     def visit_include(self, node, parent):
         """
-        <include
-          id = ID
-          schemaLocation = anyURI
-          {any attributes with non-schema Namespace}...>
-        Content: (annotation?)
-        </include>
+
+        Definition::
+
+            <include
+              id = ID
+              schemaLocation = anyURI
+              {any attributes with non-schema Namespace}...>
+            Content: (annotation?)
+            </include>
+
+        :param node: The XML node
+        :type node: lxml.etree._Element
+        :param parent: The parent XML node
+        :type parent: lxml.etree._Element
+
         """
         if not node.get('schemaLocation'):
             raise NotImplementedError("schemaLocation is required")
@@ -188,13 +237,49 @@ class SchemaVisitor(object):
             return
 
         schema_node = load_external(
-            location, self.schema._transport, base_url=self.document._base_url)
+            location, self.schema._transport,
+            base_url=self.document._base_url,
+            strict=self.schema.strict)
         self._includes.add(location)
 
-        return self.visit_schema(schema_node)
+        # When the included document has no default namespace defined but the
+        # parent document does have this then we should (atleast for #360)
+        # transfer the default namespace to the included schema. We can't
+        # update the nsmap of elements in lxml so we create a new schema with
+        # the correct nsmap and move all the content there.
+        if not schema_node.nsmap.get(None) and node.nsmap.get(None):
+            nsmap = {None: node.nsmap[None]}
+            nsmap.update(schema_node.nsmap)
+            new = etree.Element(schema_node.tag, nsmap=nsmap)
+            for child in schema_node:
+                new.append(child)
+            for key, value in schema_node.attrib.items():
+                new.set(key, value)
+            schema_node = new
+
+        # Use the element/attribute form defaults from the schema while
+        # processing the nodes.
+        element_form_default = self.document._element_form
+        attribute_form_default = self.document._attribute_form
+        base_url = self.document._base_url
+
+        self.document._element_form = schema_node.get('elementFormDefault', 'unqualified')
+        self.document._attribute_form = schema_node.get('attributeFormDefault', 'unqualified')
+        self.document._base_url = absolute_location(location, self.document._base_url)
+
+        # Iterate directly over the children.
+        for child in schema_node:
+            self.process(child, parent=schema_node)
+
+        self.document._element_form = element_form_default
+        self.document._attribute_form = attribute_form_default
+        self.document._base_url = base_url
 
     def visit_element(self, node, parent):
         """
+
+        Definition::
+
             <element
               abstract = Boolean : false
               block = (#all | List of (extension | restriction | substitution))
@@ -214,6 +299,12 @@ class SchemaVisitor(object):
             Content: (annotation?, (
                       (simpleType | complexType)?, (unique | key | keyref)*))
             </element>
+
+        :param node: The XML node
+        :type node: lxml.etree._Element
+        :param parent: The parent XML node
+        :type parent: lxml.etree._Element
+
         """
         is_global = parent.tag == tags.schema
 
@@ -272,16 +363,16 @@ class SchemaVisitor(object):
             min_occurs=min_occurs, max_occurs=max_occurs, nillable=nillable,
             default=default, is_global=is_global)
 
-        self.document._elm_instances.append(element)
-
         # Only register global elements
         if is_global:
-            self.document.register_element(qname, element)
+            self.register_element(qname, element)
         return element
 
     def visit_attribute(self, node, parent):
         """Declares an attribute.
 
+        Definition::
+
             <attribute
               default = string
               fixed = string
@@ -294,6 +385,12 @@ class SchemaVisitor(object):
               {any attributes with non-schema Namespace...}>
             Content: (annotation?, (simpleType?))
             </attribute>
+
+        :param node: The XML node
+        :type node: lxml.etree._Element
+        :param parent: The parent XML node
+        :type parent: lxml.etree._Element
+
         """
         is_global = parent.tag == tags.schema
 
@@ -303,9 +400,8 @@ class SchemaVisitor(object):
             match = re.match('([^\[]+)', array_type)
             if match:
                 array_type = match.groups()[0]
-                qname = as_qname(
-                    array_type, node.nsmap, self.document._target_namespace)
-                array_type = xsd_types.UnresolvedType(qname, self.schema)
+                qname = as_qname(array_type, node.nsmap)
+                array_type = UnresolvedType(qname, self.schema)
 
         # If the elment has a ref attribute then all other attributes cannot
         # be present. Short circuit that here.
@@ -316,9 +412,8 @@ class SchemaVisitor(object):
                 return result
 
         attribute_form = node.get('form', self.document._attribute_form)
-        qname = qname_attr(node, 'name', self.document._target_namespace)
         if attribute_form == 'qualified' or is_global:
-            name = qname
+            name = qname_attr(node, 'name', self.document._target_namespace)
         else:
             name = etree.QName(node.get('name'))
 
@@ -338,15 +433,16 @@ class SchemaVisitor(object):
 
         attr = xsd_elements.Attribute(
             name, type_=xsd_type, default=default, required=required)
-        self.document._elm_instances.append(attr)
 
         # Only register global elements
         if is_global:
-            self.document.register_attribute(qname, attr)
+            self.register_attribute(name, attr)
         return attr
 
     def visit_simple_type(self, node, parent):
         """
+        Definition::
+
             <simpleType
               final = (#all | (list | union | restriction))
               id = ID
@@ -354,6 +450,12 @@ class SchemaVisitor(object):
               {any attributes with non-schema Namespace}...>
             Content: (annotation?, (restriction | list | union))
             </simpleType>
+
+        :param node: The XML node
+        :type node: lxml.etree._Element
+        :param parent: The parent XML node
+        :type parent: lxml.etree._Element
+
         """
 
         if parent.tag == tags.schema:
@@ -369,8 +471,7 @@ class SchemaVisitor(object):
         child = items[0]
         if child.tag == tags.restriction:
             base_type = self.visit_restriction_simple_type(child, node)
-            xsd_type = xsd_types.UnresolvedCustomType(
-                qname, base_type, self.schema)
+            xsd_type = UnresolvedCustomType(qname, base_type, self.schema)
 
         elif child.tag == tags.list:
             xsd_type = self.visit_list(child, node)
@@ -382,23 +483,30 @@ class SchemaVisitor(object):
 
         assert xsd_type is not None
         if is_global:
-            self.document.register_type(qname, xsd_type)
+            self.register_type(qname, xsd_type)
         return xsd_type
 
     def visit_complex_type(self, node, parent):
         """
-        <complexType
-          abstract = Boolean : false
-          block = (#all | List of (extension | restriction))
-          final = (#all | List of (extension | restriction))
-          id = ID
-          mixed = Boolean : false
-          name = NCName
-          {any attributes with non-schema Namespace...}>
-        Content: (annotation?, (simpleContent | complexContent |
-                  ((group | all | choice | sequence)?,
-                  ((attribute | attributeGroup)*, anyAttribute?))))
-        </complexType>
+        Definition::
+
+            <complexType
+              abstract = Boolean : false
+              block = (#all | List of (extension | restriction))
+              final = (#all | List of (extension | restriction))
+              id = ID
+              mixed = Boolean : false
+              name = NCName
+              {any attributes with non-schema Namespace...}>
+            Content: (annotation?, (simpleContent | complexContent |
+                      ((group | all | choice | sequence)?,
+                      ((attribute | attributeGroup)*, anyAttribute?))))
+            </complexType>
+
+        :param node: The XML node
+        :type node: lxml.etree._Element
+        :param parent: The parent XML node
+        :type parent: lxml.etree._Element
 
         """
         children = []
@@ -448,22 +556,30 @@ class SchemaVisitor(object):
                 element=element, attributes=attributes, qname=qname,
                 is_global=is_global)
         else:
-            xsd_type = xsd_cls(qname=qname)
+            xsd_type = xsd_cls(qname=qname, is_global=is_global)
 
         if is_global:
-            self.document.register_type(qname, xsd_type)
+            self.register_type(qname, xsd_type)
         return xsd_type
 
-    def visit_complex_content(self, node, parent, namespace=None):
+    def visit_complex_content(self, node, parent):
         """The complexContent element defines extensions or restrictions on a
         complex type that contains mixed content or elements only.
 
+        Definition::
+
             <complexContent
               id = ID
               mixed = Boolean
               {any attributes with non-schema Namespace}...>
             Content: (annotation?,  (restriction | extension))
             </complexContent>
+
+        :param node: The XML node
+        :type node: lxml.etree._Element
+        :param parent: The parent XML node
+        :type parent: lxml.etree._Element
+
         """
 
         child = node.getchildren()[-1]
@@ -485,16 +601,24 @@ class SchemaVisitor(object):
                 'extension': base,
             }
 
-    def visit_simple_content(self, node, parent, namespace=None):
+    def visit_simple_content(self, node, parent):
         """Contains extensions or restrictions on a complexType element with
         character data or a simpleType element as content and contains no
         elements.
 
+        Definition::
+
             <simpleContent
               id = ID
               {any attributes with non-schema Namespace}...>
             Content: (annotation?, (restriction | extension))
             </simpleContent>
+
+        :param node: The XML node
+        :type node: lxml.etree._Element
+        :param parent: The parent XML node
+        :type parent: lxml.etree._Element
+
         """
 
         child = node.getchildren()[-1]
@@ -505,8 +629,10 @@ class SchemaVisitor(object):
             return self.visit_extension_simple_content(child, node)
         raise AssertionError("Expected restriction or extension")
 
-    def visit_restriction_simple_type(self, node, parent, namespace=None):
+    def visit_restriction_simple_type(self, node, parent):
         """
+        Definition::
+
             <restriction
               base = QName
               id = ID
@@ -517,6 +643,12 @@ class SchemaVisitor(object):
                     totalDigits |fractionDigits | length | minLength |
                     maxLength | enumeration | whiteSpace | pattern)*))
             </restriction>
+
+        :param node: The XML node
+        :type node: lxml.etree._Element
+        :param parent: The parent XML node
+        :type parent: lxml.etree._Element
+
         """
         base_name = qname_attr(node, 'base')
         if base_name:
@@ -526,8 +658,10 @@ class SchemaVisitor(object):
         if children[0].tag == tags.simpleType:
             return self.visit_simple_type(children[0], node)
 
-    def visit_restriction_simple_content(self, node, parent, namespace=None):
+    def visit_restriction_simple_content(self, node, parent):
         """
+        Definition::
+
             <restriction
               base = QName
               id = ID
@@ -539,14 +673,22 @@ class SchemaVisitor(object):
                     maxLength | enumeration | whiteSpace | pattern)*
                 )?, ((attribute | attributeGroup)*, anyAttribute?))
             </restriction>
+
+        :param node: The XML node
+        :type node: lxml.etree._Element
+        :param parent: The parent XML node
+        :type parent: lxml.etree._Element
+
         """
         base_name = qname_attr(node, 'base')
         base_type = self._get_type(base_name)
         return base_type, []
 
-    def visit_restriction_complex_content(self, node, parent, namespace=None):
+    def visit_restriction_complex_content(self, node, parent):
         """
 
+        Definition::
+
             <restriction
               base = QName
               id = ID
@@ -554,6 +696,12 @@ class SchemaVisitor(object):
             Content: (annotation?, (group | all | choice | sequence)?,
                     ((attribute | attributeGroup)*, anyAttribute?))
             </restriction>
+
+        :param node: The XML node
+        :type node: lxml.etree._Element
+        :param parent: The parent XML node
+        :type parent: lxml.etree._Element
+
         """
         base_name = qname_attr(node, 'base')
         base_type = self._get_type(base_name)
@@ -572,6 +720,9 @@ class SchemaVisitor(object):
 
     def visit_extension_complex_content(self, node, parent):
         """
+
+        Definition::
+
             <extension
               base = QName
               id = ID
@@ -580,6 +731,12 @@ class SchemaVisitor(object):
                         (group | all | choice | sequence)?,
                         ((attribute | attributeGroup)*, anyAttribute?)))
             </extension>
+
+        :param node: The XML node
+        :type node: lxml.etree._Element
+        :param parent: The parent XML node
+        :type parent: lxml.etree._Element
+
         """
         base_name = qname_attr(node, 'base')
         base_type = self._get_type(base_name)
@@ -599,6 +756,9 @@ class SchemaVisitor(object):
 
     def visit_extension_simple_content(self, node, parent):
         """
+
+        Definition::
+
             <extension
               base = QName
               id = ID
@@ -616,16 +776,27 @@ class SchemaVisitor(object):
     def visit_annotation(self, node, parent):
         """Defines an annotation.
 
+        Definition::
+
             <annotation
               id = ID
               {any attributes with non-schema Namespace}...>
             Content: (appinfo | documentation)*
             </annotation>
+
+        :param node: The XML node
+        :type node: lxml.etree._Element
+        :param parent: The parent XML node
+        :type parent: lxml.etree._Element
+
         """
         return
 
     def visit_any(self, node, parent):
         """
+
+        Definition::
+
             <any
               id = ID
               maxOccurs = (nonNegativeInteger | unbounded) : 1
@@ -636,6 +807,12 @@ class SchemaVisitor(object):
               {any attributes with non-schema Namespace...}>
             Content: (annotation?)
             </any>
+
+        :param node: The XML node
+        :type node: lxml.etree._Element
+        :param parent: The parent XML node
+        :type parent: lxml.etree._Element
+
         """
         min_occurs, max_occurs = _process_occurs_attrs(node)
         process_contents = node.get('processContents', 'strict')
@@ -645,6 +822,8 @@ class SchemaVisitor(object):
 
     def visit_sequence(self, node, parent):
         """
+        Definition::
+
             <sequence
               id = ID
               maxOccurs = (nonNegativeInteger | unbounded) : 1
@@ -653,6 +832,12 @@ class SchemaVisitor(object):
             Content: (annotation?,
                       (element | group | choice | sequence | any)*)
             </sequence>
+
+        :param node: The XML node
+        :type node: lxml.etree._Element
+        :param parent: The parent XML node
+        :type parent: lxml.etree._Element
+
         """
 
         sub_types = [
@@ -677,6 +862,8 @@ class SchemaVisitor(object):
         """Allows the elements in the group to appear (or not appear) in any
         order in the containing element.
 
+        Definition::
+
             <all
               id = ID
               maxOccurs= 1: 1
@@ -684,6 +871,12 @@ class SchemaVisitor(object):
               {any attributes with non-schema Namespace...}>
             Content: (annotation?, element*)
             </all>
+
+        :param node: The XML node
+        :type node: lxml.etree._Element
+        :param parent: The parent XML node
+        :type parent: lxml.etree._Element
+
         """
 
         sub_types = [
@@ -703,6 +896,8 @@ class SchemaVisitor(object):
         """Groups a set of element declarations so that they can be
         incorporated as a group into complex type definitions.
 
+        Definition::
+
             <group
               name= NCName
               id = ID
@@ -714,9 +909,16 @@ class SchemaVisitor(object):
             Content: (annotation?, (all | choice | sequence))
             </group>
 
+        :param node: The XML node
+        :type node: lxml.etree._Element
+        :param parent: The parent XML node
+        :type parent: lxml.etree._Element
+
         """
+        min_occurs, max_occurs = _process_occurs_attrs(node)
 
-        result = self.process_reference(node)
+        result = self.process_reference(
+            node, min_occurs=min_occurs, max_occurs=max_occurs)
         if result:
             return result
 
@@ -730,11 +932,13 @@ class SchemaVisitor(object):
         elm = xsd_elements.Group(name=qname, child=item)
 
         if parent.tag == tags.schema:
-            self.document.register_group(qname, elm)
+            self.register_group(qname, elm)
         return elm
 
     def visit_list(self, node, parent):
         """
+        Definition::
+
             <list
               id = ID
               itemType = QName
@@ -745,6 +949,12 @@ class SchemaVisitor(object):
         The use of the simpleType element child and the itemType attribute is
         mutually exclusive.
 
+        :param node: The XML node
+        :type node: lxml.etree._Element
+        :param parent: The parent XML node
+        :type parent: lxml.etree._Element
+
+
         """
         item_type = qname_attr(node, 'itemType')
         if item_type:
@@ -757,6 +967,8 @@ class SchemaVisitor(object):
 
     def visit_choice(self, node, parent):
         """
+        Definition::
+
             <choice
               id = ID
               maxOccurs= (nonNegativeInteger | unbounded) : 1
@@ -780,19 +992,27 @@ class SchemaVisitor(object):
     def visit_union(self, node, parent):
         """Defines a collection of multiple simpleType definitions.
 
+        Definition::
+
             <union
               id = ID
               memberTypes = List of QNames
               {any attributes with non-schema Namespace}...>
             Content: (annotation?, (simpleType*))
             </union>
+
+        :param node: The XML node
+        :type node: lxml.etree._Element
+        :param parent: The parent XML node
+        :type parent: lxml.etree._Element
+
         """
         # TODO
         members = node.get('memberTypes')
         types = []
         if members:
             for member in members.split():
-                qname = as_qname(member, node.nsmap, self.document._target_namespace)
+                qname = as_qname(member, node.nsmap)
                 xsd_type = self._get_type(qname)
                 types.append(xsd_type)
         else:
@@ -805,18 +1025,28 @@ class SchemaVisitor(object):
         attribute or element values) must be unique within the specified scope.
         The value must be unique or nil.
 
+        Definition::
+
             <unique
               id = ID
               name = NCName
               {any attributes with non-schema Namespace}...>
             Content: (annotation?, (selector, field+))
             </unique>
+
+        :param node: The XML node
+        :type node: lxml.etree._Element
+        :param parent: The parent XML node
+        :type parent: lxml.etree._Element
+
         """
         # TODO
         pass
 
     def visit_attribute_group(self, node, parent):
         """
+        Definition::
+
             <attributeGroup
               id = ID
               name = NCName
@@ -825,6 +1055,12 @@ class SchemaVisitor(object):
             Content: (annotation?),
                      ((attribute | attributeGroup)*, anyAttribute?))
             </attributeGroup>
+
+        :param node: The XML node
+        :type node: lxml.etree._Element
+        :param parent: The parent XML node
+        :type parent: lxml.etree._Element
+
         """
         ref = self.process_reference(node)
         if ref:
@@ -835,10 +1071,12 @@ class SchemaVisitor(object):
 
         attributes = self._process_attributes(node, children)
         attribute_group = xsd_elements.AttributeGroup(qname, attributes)
-        self.document.register_attribute_group(qname, attribute_group)
+        self.register_attribute_group(qname, attribute_group)
 
     def visit_any_attribute(self, node, parent):
         """
+        Definition::
+
             <anyAttribute
               id = ID
               namespace = ((##any | ##other) |
@@ -847,6 +1085,12 @@ class SchemaVisitor(object):
               {any attributes with non-schema Namespace...}>
             Content: (annotation?)
             </anyAttribute>
+
+        :param node: The XML node
+        :type node: lxml.etree._Element
+        :param parent: The parent XML node
+        :type parent: lxml.etree._Element
+
         """
         process_contents = node.get('processContents', 'strict')
         return xsd_elements.AnyAttribute(process_contents=process_contents)
@@ -856,6 +1100,8 @@ class SchemaVisitor(object):
         non-XML data within an XML document. An XML Schema notation declaration
         is a reconstruction of XML 1.0 NOTATION declarations.
 
+        Definition::
+
             <notation
               id = ID
               name = NCName
@@ -865,17 +1111,18 @@ class SchemaVisitor(object):
             Content: (annotation?)
             </notation>
 
+        :param node: The XML node
+        :type node: lxml.etree._Element
+        :param parent: The parent XML node
+        :type parent: lxml.etree._Element
+
         """
         pass
 
     def _get_type(self, name):
         assert name is not None
         name = self._create_qname(name)
-        try:
-            retval = self.schema.get_type(name)
-        except (exceptions.NamespaceError, exceptions.LookupError):
-            retval = xsd_types.UnresolvedType(name, self.schema)
-        return retval
+        return UnresolvedType(name, self.schema)
 
     def _create_qname(self, name):
         if not isinstance(name, etree.QName):
diff --git a/tests/test_asyncio_transport.py b/tests/test_asyncio_transport.py
index 2c65a45..7aca012 100644
--- a/tests/test_asyncio_transport.py
+++ b/tests/test_asyncio_transport.py
@@ -1,6 +1,7 @@
 import pytest
 from pretend import stub
 from lxml import etree
+import aiohttp
 from aioresponses import aioresponses
 
 from zeep import cache, asyncio
@@ -39,3 +40,21 @@ async def test_post(event_loop):
             headers={})
 
         assert result.content == b'x'
+
+
+ at pytest.mark.requests
+ at pytest.mark.asyncio
+async def test_session_close(event_loop):
+    transport = asyncio.AsyncTransport(loop=event_loop)
+    session = transport.session  # copy session object from transport
+    del transport
+    assert session.closed
+
+
+ at pytest.mark.requests
+ at pytest.mark.asyncio
+async def test_session_no_close(event_loop):
+    session = aiohttp.ClientSession(loop=event_loop)
+    transport = asyncio.AsyncTransport(loop=event_loop, session=session)
+    del transport
+    assert not session.closed
diff --git a/tests/test_client_factory.py b/tests/test_client_factory.py
index 92d5b1a..632ae78 100644
--- a/tests/test_client_factory.py
+++ b/tests/test_client_factory.py
@@ -11,6 +11,18 @@ def test_factory_namespace():
     assert obj.NameLast == 'van Tellingen'
 
 
+def test_factory_no_reference():
+    client = Client('tests/wsdl_files/soap.wsdl')
+    factory = client.type_factory('http://example.com/stockquote.xsd')
+    obj_1 = client.get_type('ns0:ArrayOfAddress')()
+    obj_1.Address.append({
+        'NameFirst': 'J',
+        'NameLast': 'Doe',
+    })
+    obj_2 = client.get_type('ns0:ArrayOfAddress')()
+    assert len(obj_2.Address) == 0
+
+
 def test_factory_ns_auto_prefix():
     client = Client('tests/wsdl_files/soap.wsdl')
     factory = client.type_factory('ns0')
diff --git a/tests/test_loader.py b/tests/test_loader.py
new file mode 100644
index 0000000..83ce9a7
--- /dev/null
+++ b/tests/test_loader.py
@@ -0,0 +1,14 @@
+from zeep.loader import parse_xml
+from tests.utils import DummyTransport
+
+def test_huge_text():
+    # libxml2>=2.7.3 has XML_MAX_TEXT_LENGTH 10000000 without XML_PARSE_HUGE
+    tree = parse_xml(u"""
+        <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
+         <s:Body>
+          <HugeText xmlns="http://hugetext">%s</HugeText>
+         </s:Body>
+        </s:Envelope>
+    """ % (u'\u00e5' * 10000001), DummyTransport(), xml_huge_tree=True)
+
+    assert tree[0][0].text == u'\u00e5' * 10000001
diff --git a/tests/test_soap_multiref.py b/tests/test_soap_multiref.py
new file mode 100644
index 0000000..ae9ab7e
--- /dev/null
+++ b/tests/test_soap_multiref.py
@@ -0,0 +1,134 @@
+import io
+
+import pytest
+import requests_mock
+from lxml import etree
+from pretend import stub
+from six import StringIO
+
+from tests.utils import DummyTransport, assert_nodes_equal
+from zeep import Client, wsdl
+from zeep.transports import Transport
+
+
+ at pytest.mark.requests
+def test_parse_soap_wsdl():
+    wsdl_file = io.StringIO(u"""
+        <?xml version="1.0"?>
+        <wsdl:definitions
+          xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
+          xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+          xmlns:tns="http://tests.python-zeep.org/"
+          xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
+          xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/"
+          targetNamespace="http://tests.python-zeep.org/">
+
+          <wsdl:types>
+            <xsd:schema
+                targetNamespace="http://tests.python-zeep.org/"
+                xmlns:tns="http://tests.python-zeep.org/"
+                elementFormDefault="qualified">
+              <xsd:element name="input" type="xsd:string"/>
+
+              <xsd:element name="output">
+                <xsd:complexType>
+                  <xsd:sequence>
+                    <xsd:element name="item_1" type="tns:type_1"/>
+                    <xsd:element name="item_2" type="tns:type_2"/>
+                  </xsd:sequence>
+                </xsd:complexType>
+             </xsd:element>
+
+              <xsd:complexType name="type_1">
+                <xsd:sequence>
+                  <xsd:element name="subitem_1" type="xsd:string"/>
+                  <xsd:element name="subitem_2" type="xsd:string"/>
+                </xsd:sequence>
+              </xsd:complexType>
+              <xsd:complexType name="type_2">
+                <xsd:sequence>
+                  <xsd:element name="subitem_1" type="tns:type_1"/>
+                  <xsd:element name="subitem_2" type="xsd:string"/>
+                </xsd:sequence>
+              </xsd:complexType>
+            </xsd:schema>
+          </wsdl:types>
+
+          <wsdl:message name="TestOperationRequest">
+            <wsdl:part name="response" element="tns:input"/>
+          </wsdl:message>
+
+          <wsdl:message name="TestOperationResponse">
+            <wsdl:part name="response" element="tns:output"/>
+          </wsdl:message>
+
+          <wsdl:portType name="TestPortType">
+            <wsdl:operation name="TestOperation">
+              <wsdl:input message="TestOperationRequest"/>
+              <wsdl:output message="TestOperationResponse"/>
+            </wsdl:operation>
+          </wsdl:portType>
+
+          <wsdl:binding name="TestBinding" type="tns:TestPortType">
+            <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
+            <wsdl:operation name="TestOperation">
+              <soap:operation soapAction=""/>
+              <wsdl:input name="TestOperationRequest">
+                <soap:body use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" />
+              </wsdl:input>
+              <wsdl:output name="TestOperationResponse">
+                <soap:body use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" />
+              </wsdl:output>
+            </wsdl:operation>
+          </wsdl:binding>
+          <wsdl:service name="TestService">
+            <wsdl:documentation>Test service</wsdl:documentation>
+            <wsdl:port name="TestPortType" binding="tns:TestBinding">
+              <soap:address location="http://tests.python-zeep.org/test"/>
+            </wsdl:port>
+          </wsdl:service>
+        </wsdl:definitions>
+    """.strip())
+
+    content = """
+        <?xml version="1.0"?>
+        <soapenv:Envelope
+            xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
+            xmlns:tns="http://tests.python-zeep.org/">
+           <soapenv:Body>
+              <tns:TestOperationResponse>
+                <tns:output>
+                  <tns:item_1 href="#id0"/>
+                  <tns:item_2>
+                    <tns:subitem_1>
+                      <tns:subitem_1>foo</tns:subitem_1>
+                      <tns:subitem_2>bar</tns:subitem_2>
+                    </tns:subitem_1>
+                  <tns:subitem_2>bar</tns:subitem_2>
+                  </tns:item_2>
+                </tns:output>
+              </tns:TestOperationResponse>
+
+              <multiRef id="id0">
+                <tns:subitem_1>foo</tns:subitem_1>
+                <tns:subitem_2>bar</tns:subitem_2>
+              </multiRef>
+           </soapenv:Body>
+        </soapenv:Envelope>
+    """.strip()
+
+    client = Client(wsdl_file, transport=Transport(),)
+    response = stub(
+        status_code=200,
+        headers={},
+        content=content)
+
+    operation = client.service._binding._operations['TestOperation']
+    result = client.service._binding.process_reply(
+        client, operation, response)
+
+    assert result.item_1.subitem_1 == 'foo'
+    assert result.item_1.subitem_2 == 'bar'
+    assert result.item_2.subitem_1.subitem_1 == 'foo'
+    assert result.item_2.subitem_1.subitem_2 == 'bar'
+    assert result.item_2.subitem_2 == 'bar'
diff --git a/tests/test_wsdl.py b/tests/test_wsdl.py
index f42ba4c..2c34ac3 100644
--- a/tests/test_wsdl.py
+++ b/tests/test_wsdl.py
@@ -100,7 +100,8 @@ def test_parse_soap_header_wsdl():
                 }
             })
 
-        assert result == 120.123
+        assert result.body.price == 120.123
+        assert result.header.body is None
 
         request = m.request_history[0]
 
diff --git a/tests/test_wsdl_arrays.py b/tests/test_wsdl_arrays.py
index 21b96d1..f64b7d2 100644
--- a/tests/test_wsdl_arrays.py
+++ b/tests/test_wsdl_arrays.py
@@ -1,12 +1,14 @@
 import io
 
+import pytest
 from lxml import etree
 
-from tests.utils import DummyTransport, assert_nodes_equal, load_xml
+from tests.utils import DummyTransport, assert_nodes_equal, load_xml, render_node
 from zeep import xsd
 
 
-def get_transport():
+ at pytest.fixture(scope='function')
+def transport():
     transport = DummyTransport()
     transport.bind(
         'http://schemas.xmlsoap.org/soap/encoding/',
@@ -14,7 +16,7 @@ def get_transport():
     return transport
 
 
-def test_simple_type():
+def test_simple_type(transport):
     schema = xsd.Schema(load_xml("""
     <xsd:schema
         xmlns:xsd="http://www.w3.org/2001/XMLSchema"
@@ -30,7 +32,7 @@ def test_simple_type():
         </xsd:complexContent>
       </xsd:complexType>
     </xsd:schema>
-    """), transport=get_transport())
+    """), transport=transport)
 
     ArrayOfString = schema.get_type('ns0:ArrayOfString')
     print(ArrayOfString.__dict__)
@@ -52,8 +54,105 @@ def test_simple_type():
 
     assert_nodes_equal(expected, node)
 
+    data = ArrayOfString.parse_xmlelement(node, schema)
+    assert data == ['item', 'and', 'even', 'more', 'items']
+    assert data.as_value_object()
 
-def test_complex_type():
+
+def test_simple_type_nested(transport):
+    schema = xsd.Schema(load_xml("""
+    <xsd:schema
+        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+        xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
+        xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
+        targetNamespace="http://tests.python-zeep.org/tns"
+        xmlns:tns="http://tests.python-zeep.org/tns">
+      <xsd:import namespace="http://schemas.xmlsoap.org/soap/encoding/"/>
+      <xsd:complexType name="container">
+        <xsd:sequence>
+          <xsd:element name="strings" type="tns:ArrayOfString"/>
+        </xsd:sequence>
+      </xsd:complexType>
+
+      <xsd:complexType name="ArrayOfString">
+        <xsd:complexContent>
+          <xsd:restriction base="SOAP-ENC:Array">
+            <xsd:attribute ref="SOAP-ENC:arrayType" wsdl:arrayType="xsd:string[]"/>
+          </xsd:restriction>
+        </xsd:complexContent>
+      </xsd:complexType>
+    </xsd:schema>
+    """), transport=transport)
+
+    Container = schema.get_type('ns0:container')
+    value = Container(strings=['item', 'and', 'even', 'more', 'items'])
+
+    assert value.strings == ['item', 'and', 'even', 'more', 'items']
+
+    node = etree.Element('document')
+    Container.render(node, value)
+
+    expected = """
+        <document>
+            <strings>
+              <item xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">item</item>
+              <item xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">and</item>
+              <item xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">even</item>
+              <item xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">more</item>
+              <item xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">items</item>
+            </strings>
+        </document>
+    """  # noqa
+
+    assert_nodes_equal(expected, node)
+
+    data = Container.parse_xmlelement(node, schema)
+    assert data.strings == ['item', 'and', 'even', 'more', 'items']
+
+
+def test_simple_type_nested_inline_type(transport):
+    schema = xsd.Schema(load_xml("""
+    <xsd:schema
+        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+        xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
+        xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
+        targetNamespace="http://tests.python-zeep.org/tns"
+        xmlns:tns="http://tests.python-zeep.org/tns">
+      <xsd:import namespace="http://schemas.xmlsoap.org/soap/encoding/"/>
+      <xsd:complexType name="container">
+        <xsd:sequence>
+          <xsd:element name="strings" type="tns:ArrayOfString"/>
+        </xsd:sequence>
+      </xsd:complexType>
+
+      <xsd:complexType name="ArrayOfString">
+        <xsd:complexContent>
+          <xsd:restriction base="SOAP-ENC:Array">
+            <xsd:attribute ref="SOAP-ENC:arrayType" wsdl:arrayType="xsd:string[]"/>
+          </xsd:restriction>
+        </xsd:complexContent>
+      </xsd:complexType>
+    </xsd:schema>
+    """), transport=transport)
+
+    Container = schema.get_type('ns0:container')
+    node = load_xml("""
+        <document xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+            <strings xsi:type="soapenc:Array" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
+              <item xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">item</item>
+              <item xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">and</item>
+              <item xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">even</item>
+              <item xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">more</item>
+              <item xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">items</item>
+            </strings>
+        </document>
+    """)  # noqa
+
+    data = Container.parse_xmlelement(node, schema)
+    assert data.strings == ['item', 'and', 'even', 'more', 'items']
+
+
+def test_complex_type(transport):
     schema = xsd.Schema(load_xml("""
     <xsd:schema
         xmlns:xsd="http://www.w3.org/2001/XMLSchema"
@@ -80,7 +179,7 @@ def test_complex_type():
         </xsd:complexContent>
       </xsd:complexType>
     </xsd:schema>
-    """), transport=get_transport())
+    """), transport=transport)
 
     ArrayOfObject = schema.get_type('ns0:ArrayOfObject')
     ArrayObject = schema.get_type('ns0:ArrayObject')
@@ -111,9 +210,17 @@ def test_complex_type():
         </document>
     """
     assert_nodes_equal(expected, node)
+    data = ArrayOfObject.parse_xmlelement(node, schema)
+
+    assert data[0].attr_1 == 'attr-1'
+    assert data[0].attr_2 == 'attr-2'
+    assert data[1].attr_1 == 'attr-3'
+    assert data[1].attr_2 == 'attr-4'
+    assert data[2].attr_1 == 'attr-5'
+    assert data[2].attr_2 == 'attr-6'
 
 
-def test_complex_type_without_name():
+def test_complex_type_without_name(transport):
     schema = xsd.Schema(load_xml("""
     <xsd:schema
         xmlns:xsd="http://www.w3.org/2001/XMLSchema"
@@ -137,7 +244,7 @@ def test_complex_type_without_name():
         </xsd:complexContent>
       </xsd:complexType>
     </xsd:schema>
-    """), transport=get_transport())
+    """), transport=transport)
 
     ArrayOfObject = schema.get_type('ns0:ArrayOfObject')
     ArrayObject = schema.get_type('ns0:ArrayObject')
@@ -170,13 +277,13 @@ def test_complex_type_without_name():
     assert_nodes_equal(expected, node)
     data = ArrayOfObject.parse_xmlelement(node, schema)
 
-    assert len(data._value_1) == 3
-    assert data._value_1[0]['attr_1'] == 'attr-1'
-    assert data._value_1[0]['attr_2'] == 'attr-2'
-    assert data._value_1[1]['attr_1'] == 'attr-3'
-    assert data._value_1[1]['attr_2'] == 'attr-4'
-    assert data._value_1[2]['attr_1'] == 'attr-5'
-    assert data._value_1[2]['attr_2'] == 'attr-6'
+    assert len(data) == 3
+    assert data[0]['attr_1'] == 'attr-1'
+    assert data[0]['attr_2'] == 'attr-2'
+    assert data[1]['attr_1'] == 'attr-3'
+    assert data[1]['attr_2'] == 'attr-4'
+    assert data[2]['attr_1'] == 'attr-5'
+    assert data[2]['attr_2'] == 'attr-6'
 
 
 def test_soap_array_parse_remote_ns():
@@ -237,8 +344,8 @@ def test_soap_array_parse_remote_ns():
     elm = schema.get_element('ns0:countries')
     data = elm.parse(doc, schema)
 
-    assert data._value_1[0].code == 'NL'
-    assert data._value_1[0].name == 'The Netherlands'
+    assert data[0].code == 'NL'
+    assert data[0].name == 'The Netherlands'
 
 
 def test_wsdl_array_type():
@@ -284,9 +391,12 @@ def test_wsdl_array_type():
 
     array = array_elm([item_1, item_2])
     node = etree.Element('document')
-    assert array_elm.signature() == (
-        '_value_1: base[], arrayType: xsd:string, offset: arrayCoordinate, ' +
-        'id: xsd:ID, href: xsd:anyURI, _attr_1: {}')
+    assert array_elm.signature(schema=schema) == 'ns0:array(ns0:array)'
+
+    array_type = schema.get_type('ns0:array')
+    assert array_type.signature(schema=schema) == (
+        'ns0:array(_value_1: base[], arrayType: xsd:string, ' +
+        'offset: ns1:arrayCoordinate, id: xsd:ID, href: xsd:anyURI, _attr_1: {})')
     array_elm.render(node, array)
     expected = """
         <document>
@@ -363,7 +473,80 @@ def test_soap_array_parse():
 
     elm = schema.get_element('ns0:FlagDetailsList')
     data = elm.parse(doc, schema)
-    assert data.FlagDetailsStruct[0].Name == 'flag1'
-    assert data.FlagDetailsStruct[0].Value == 'value1'
-    assert data.FlagDetailsStruct[1].Name == 'flag2'
-    assert data.FlagDetailsStruct[1].Value == 'value2'
+    assert data[0].Name == 'flag1'
+    assert data[0].Value == 'value1'
+    assert data[1].Name == 'flag2'
+    assert data[1].Value == 'value2'
+
+
+def test_xml_soap_enc_string(transport):
+    schema = xsd.Schema(load_xml("""
+        <?xml version="1.0"?>
+        <xsd:schema
+            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+            xmlns:tns="http://tests.python-zeep.org/"
+            xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
+            xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
+            targetNamespace="http://tests.python-zeep.org/"
+            elementFormDefault="qualified">
+          <xsd:import namespace="http://schemas.xmlsoap.org/soap/encoding/"/>
+
+          <xsd:element name="value" type="tns:ArrayOfString"/>
+
+          <xsd:complexType name="ArrayOfString">
+            <xsd:complexContent>
+              <xsd:restriction base="soapenc:Array">
+                <xsd:attribute ref="soapenc:arrayType" wsdl:arrayType="soapenc:string[]"/>
+              </xsd:restriction>
+            </xsd:complexContent>
+          </xsd:complexType>
+
+        </xsd:schema>
+    """), transport)
+    shoe_type = schema.get_element('{http://tests.python-zeep.org/}value')
+
+    obj = shoe_type(["foo"])
+    node = render_node(shoe_type, obj)
+    expected = """
+        <document>
+            <ns0:value xmlns:ns0="http://tests.python-zeep.org/">
+              <string>foo</string>
+            </ns0:value>
+        </document>
+    """
+    assert_nodes_equal(expected, node)
+
+    obj = shoe_type.parse(node[0], schema)
+    assert obj[0]['_value_1'] == "foo"
+
+    # Via string-types
+    string_type = schema.get_type('{http://schemas.xmlsoap.org/soap/encoding/}string')
+    obj = shoe_type([string_type('foo')])
+    node = render_node(shoe_type, obj)
+    expected = """
+        <document>
+            <ns0:value xmlns:ns0="http://tests.python-zeep.org/">
+              <string>foo</string>
+            </ns0:value>
+        </document>
+    """
+    assert_nodes_equal(expected, node)
+
+    obj = shoe_type.parse(node[0], schema)
+    assert obj[0]['_value_1'] == "foo"
+
+    # Via dicts
+    string_type = schema.get_type('{http://schemas.xmlsoap.org/soap/encoding/}string')
+    obj = shoe_type([{'_value_1': 'foo'}])
+    node = render_node(shoe_type, obj)
+    expected = """
+        <document>
+            <ns0:value xmlns:ns0="http://tests.python-zeep.org/">
+              <string>foo</string>
+            </ns0:value>
+        </document>
+    """
+    assert_nodes_equal(expected, node)
+
+    obj = shoe_type.parse(node[0], schema)
+    assert obj[0]['_value_1'] == "foo"
diff --git a/tests/test_wsdl_messages_document.py b/tests/test_wsdl_messages_document.py
index c2a2389..151aef3 100644
--- a/tests/test_wsdl_messages_document.py
+++ b/tests/test_wsdl_messages_document.py
@@ -54,14 +54,14 @@ def test_parse():
     binding = root.bindings['{http://tests.python-zeep.org/tns}TestBinding']
     operation = binding.get('TestOperation')
 
-    assert operation.input.body.signature() == 'xsd:string'
-    assert operation.input.header.signature() == ''
-    assert operation.input.envelope.signature() == 'body: xsd:string'
+    assert operation.input.body.signature(schema=root.types) == 'ns0:Request(xsd:string)'
+    assert operation.input.header.signature(schema=root.types) == 'soap-env:Header()'
+    assert operation.input.envelope.signature(schema=root.types) == 'soap-env:envelope(body: xsd:string)'
     assert operation.input.signature(as_output=False) == 'xsd:string'
 
-    assert operation.output.body.signature() == 'xsd:string'
-    assert operation.output.header.signature() == ''
-    assert operation.output.envelope.signature() == 'body: xsd:string'
+    assert operation.output.body.signature(schema=root.types) == 'ns0:Response(xsd:string)'
+    assert operation.output.header.signature(schema=root.types) == 'soap-env:Header()'
+    assert operation.output.envelope.signature(schema=root.types) == 'soap-env:envelope(body: xsd:string)'
     assert operation.output.signature(as_output=True) == 'xsd:string'
 
 
@@ -111,9 +111,9 @@ def test_empty_input_parse():
     binding = root.bindings['{http://tests.python-zeep.org/tns}TestBinding']
     operation = binding.get('TestOperation')
 
-    assert operation.input.body.signature() == ''
-    assert operation.input.header.signature() == ''
-    assert operation.input.envelope.signature() == 'body: {}'
+    assert operation.input.body.signature(schema=root.types) == 'soap-env:Body()'
+    assert operation.input.header.signature(schema=root.types) == 'soap-env:Header()'
+    assert operation.input.envelope.signature(schema=root.types) == 'soap-env:envelope(body: {})'
     assert operation.input.signature(as_output=False) == ''
 
 
@@ -171,15 +171,15 @@ def test_parse_with_header():
     binding = root.bindings['{http://tests.python-zeep.org/tns}TestBinding']
     operation = binding.get('TestOperation')
 
-    assert operation.input.body.signature() == 'xsd:string'
-    assert operation.input.header.signature() == 'auth: RequestHeader()'
-    assert operation.input.envelope.signature() == 'body: xsd:string, header: {auth: RequestHeader()}'  # noqa
-    assert operation.input.signature(as_output=False) == 'xsd:string, _soapheaders={auth: RequestHeader()}'  # noqa
+    assert operation.input.body.signature(schema=root.types) == 'ns0:Request(xsd:string)'
+    assert operation.input.header.signature(schema=root.types) == 'soap-env:Header(auth: xsd:string)'
+    assert operation.input.envelope.signature(schema=root.types) == 'soap-env:envelope(header: {auth: xsd:string}, body: xsd:string)'  # noqa
+    assert operation.input.signature(as_output=False) == 'xsd:string, _soapheaders={auth: xsd:string}'  # noqa
 
-    assert operation.output.body.signature() == 'xsd:string'
-    assert operation.output.header.signature() == 'auth: ResponseHeader()'
-    assert operation.output.envelope.signature() == 'body: xsd:string, header: {auth: ResponseHeader()}'  # noqa
-    assert operation.output.signature(as_output=True) == 'body: xsd:string, header: {auth: ResponseHeader()}'  # noqa
+    assert operation.output.body.signature(schema=root.types) == 'ns0:Response(xsd:string)'
+    assert operation.output.header.signature(schema=root.types) == 'soap-env:Header(auth: xsd:string)'
+    assert operation.output.envelope.signature(schema=root.types) == 'soap-env:envelope(header: {auth: xsd:string}, body: xsd:string)'  # noqa
+    assert operation.output.signature(as_output=True) == 'header: {auth: xsd:string}, body: xsd:string'  # noqa
 
 
 def test_parse_with_header_type():
@@ -240,15 +240,15 @@ def test_parse_with_header_type():
     binding = root.bindings['{http://tests.python-zeep.org/tns}TestBinding']
     operation = binding.get('TestOperation')
 
-    assert operation.input.body.signature() == 'xsd:string'
-    assert operation.input.header.signature() == 'auth: RequestHeaderType'
-    assert operation.input.envelope.signature() == 'body: xsd:string, header: {auth: RequestHeaderType}'  # noqa
-    assert operation.input.signature(as_output=False) == 'xsd:string, _soapheaders={auth: RequestHeaderType}'  # noqa
+    assert operation.input.body.signature(schema=root.types) == 'ns0:Request(xsd:string)'
+    assert operation.input.header.signature(schema=root.types) == 'soap-env:Header(auth: ns0:RequestHeaderType)'
+    assert operation.input.envelope.signature(schema=root.types) == 'soap-env:envelope(header: {auth: ns0:RequestHeaderType}, body: xsd:string)'  # noqa
+    assert operation.input.signature(as_output=False) == 'xsd:string, _soapheaders={auth: ns0:RequestHeaderType}'  # noqa
 
-    assert operation.output.body.signature() == 'xsd:string'
-    assert operation.output.header.signature() == 'auth: ResponseHeaderType'
-    assert operation.output.envelope.signature() == 'body: xsd:string, header: {auth: ResponseHeaderType}'  # noqa
-    assert operation.output.signature(as_output=True) == 'body: xsd:string, header: {auth: ResponseHeaderType}'  # noqa
+    assert operation.output.body.signature(schema=root.types) == 'ns0:Response(xsd:string)'
+    assert operation.output.header.signature(schema=root.types) == 'soap-env:Header(auth: ns0:ResponseHeaderType)'
+    assert operation.output.envelope.signature(schema=root.types) == 'soap-env:envelope(header: {auth: ns0:ResponseHeaderType}, body: xsd:string)'  # noqa
+    assert operation.output.signature(as_output=True) == 'header: {auth: ns0:ResponseHeaderType}, body: xsd:string'  # noqa
 
 
 def test_parse_with_header_other_message():
@@ -292,12 +292,13 @@ def test_parse_with_header_other_message():
     """.strip())
 
     root = wsdl.Document(wsdl_content, None)
+    root.types.set_ns_prefix('soap-env', 'http://schemas.xmlsoap.org/soap/envelope/')
 
     binding = root.bindings['{http://tests.python-zeep.org/tns}TestBinding']
     operation = binding.get('TestOperation')
 
-    assert operation.input.header.signature() == 'header: RequestHeader()'
-    assert operation.input.body.signature() == 'xsd:string'
+    assert operation.input.header.signature(schema=root.types) == 'soap-env:Header(header: xsd:string)'
+    assert operation.input.body.signature(schema=root.types) == 'ns0:Request(xsd:string)'
 
     header = root.types.get_element(
         '{http://tests.python-zeep.org/tns}RequestHeader'
@@ -1193,7 +1194,7 @@ def test_deserialize_with_headers():
     serialized = operation.process_reply(response_body)
 
     assert operation.output.signature(as_output=True) == (
-        'body: {request_1: Request1(), request_2: Request2()}, header: {header_1: Header1(), header_2: Header2()}')  # noqa
+        'header: {header_1: ns0:Header1, header_2: xsd:string}, body: {request_1: ns0:Request1, request_2: ns0:Request2}')
     assert serialized.body.request_1.arg1 == 'ah1'
     assert serialized.body.request_2.arg2 == 'ah2'
     assert serialized.header.header_1.username == 'mvantellingen'
diff --git a/tests/test_wsdl_messages_http.py b/tests/test_wsdl_messages_http.py
index aa40d25..eb48969 100644
--- a/tests/test_wsdl_messages_http.py
+++ b/tests/test_wsdl_messages_http.py
@@ -52,10 +52,10 @@ def test_urlencoded_serialize():
     binding = root.bindings['{http://tests.python-zeep.org/tns}TestBinding']
     operation = binding.get('TestOperation')
 
-    assert operation.input.body.signature() == 'arg1: xsd:string, arg2: xsd:string'
+    assert operation.input.body.signature(schema=root.types) == 'TestOperation(arg1: xsd:string, arg2: xsd:string)'
     assert operation.input.signature(as_output=False) == 'arg1: xsd:string, arg2: xsd:string'
 
-    assert operation.output.body.signature() == 'Body: xsd:string'
+    assert operation.output.body.signature(schema=root.types) == 'TestOperation(Body: xsd:string)'
     assert operation.output.signature(as_output=True) == 'xsd:string'
 
     serialized = operation.input.serialize(arg1='ah1', arg2='ah2')
@@ -112,10 +112,10 @@ def test_urlreplacement_serialize():
     binding = root.bindings['{http://tests.python-zeep.org/tns}TestBinding']
     operation = binding.get('TestOperation')
 
-    assert operation.input.body.signature() == 'arg1: xsd:string, arg2: xsd:string'
+    assert operation.input.body.signature(schema=root.types) == 'TestOperation(arg1: xsd:string, arg2: xsd:string)'
     assert operation.input.signature(as_output=False) == 'arg1: xsd:string, arg2: xsd:string'
 
-    assert operation.output.body.signature() == 'Body: xsd:string'
+    assert operation.output.body.signature(schema=root.types) == 'TestOperation(Body: xsd:string)'
     assert operation.output.signature(as_output=True) == 'xsd:string'
 
     serialized = operation.input.serialize(arg1='ah1', arg2='ah2')
@@ -172,10 +172,10 @@ def test_mime_content_serialize_form_urlencoded():
     binding = root.bindings['{http://tests.python-zeep.org/tns}TestBinding']
     operation = binding.get('TestOperation')
 
-    assert operation.input.body.signature() == 'arg1: xsd:string, arg2: xsd:string'
+    assert operation.input.body.signature(schema=root.types) == 'TestOperation(arg1: xsd:string, arg2: xsd:string)'
     assert operation.input.signature(as_output=False) == 'arg1: xsd:string, arg2: xsd:string'
 
-    assert operation.output.body.signature() == 'Body: xsd:string'
+    assert operation.output.body.signature(schema=root.types) == 'TestOperation(Body: xsd:string)'
     assert operation.output.signature(as_output=True) == 'xsd:string'
 
     serialized = operation.input.serialize(arg1='ah1', arg2='ah2')
@@ -229,10 +229,10 @@ def test_mime_content_serialize_text_xml():
     binding = root.bindings['{http://tests.python-zeep.org/tns}TestBinding']
     operation = binding.get('TestOperation')
 
-    assert operation.input.body.signature() == 'arg1: xsd:string, arg2: xsd:string'
+    assert operation.input.body.signature(schema=root.types) == 'TestOperation(arg1: xsd:string, arg2: xsd:string)'
     assert operation.input.signature(as_output=False) == 'arg1: xsd:string, arg2: xsd:string'
 
-    assert operation.output.body.signature() == 'Body: xsd:string'
+    assert operation.output.body.signature(schema=root.types) == 'TestOperation(Body: xsd:string)'
     assert operation.output.signature(as_output=True) == 'xsd:string'
 
     serialized = operation.input.serialize(arg1='ah1', arg2='ah2')
diff --git a/tests/test_wsdl_soap.py b/tests/test_wsdl_soap.py
index 960645f..e80c152 100644
--- a/tests/test_wsdl_soap.py
+++ b/tests/test_wsdl_soap.py
@@ -1,12 +1,36 @@
+# -*- coding: utf-8 -*-
+
+import pytest
+
 from lxml import etree
 from pretend import stub
 
 from tests.utils import load_xml
 from zeep import Client
 from zeep.exceptions import Fault
+from zeep.exceptions import TransportError
 from zeep.wsdl import bindings
 
 
+def test_soap11_no_output():
+    client = Client('tests/wsdl_files/soap.wsdl')
+    content = """
+        <soapenv:Envelope
+            xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
+            xmlns:stoc="http://example.com/stockquote.xsd">
+          <soapenv:Body></soapenv:Body>
+        </soapenv:Envelope>
+    """.strip()
+    response = stub(
+        status_code=200,
+        headers={},
+        content=content)
+
+    operation = client.service._binding._operations['GetLastTradePriceNoOutput']
+    res = client.service._binding.process_reply(client, operation, response)
+    assert res is None
+
+
 def test_soap11_process_error():
     response = load_xml("""
         <soapenv:Envelope
@@ -26,10 +50,10 @@ def test_soap11_process_error():
           </soapenv:Body>
         </soapenv:Envelope>
     """)
+
     binding = bindings.Soap11Binding(
         wsdl=None, name=None, port_name=None, transport=None,
         default_style=None)
-
     try:
         binding.process_error(response, None)
         assert False
@@ -112,6 +136,79 @@ def test_soap12_process_error():
         assert exc.subcodes[1].localname == 'fault-subcode2'
 
 
+def test_no_content_type():
+    data = """
+        <?xml version="1.0"?>
+        <soapenv:Envelope
+            xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
+            xmlns:stoc="http://example.com/stockquote.xsd">
+           <soapenv:Header/>
+           <soapenv:Body>
+              <stoc:TradePrice>
+                 <price>120.123</price>
+              </stoc:TradePrice>
+           </soapenv:Body>
+        </soapenv:Envelope>
+    """.strip()
+
+    client = Client('tests/wsdl_files/soap.wsdl')
+    binding = client.service._binding
+
+    response = stub(
+        status_code=200,
+        content=data,
+        encoding='utf-8',
+        headers={}
+    )
+
+    result = binding.process_reply(
+        client, binding.get('GetLastTradePrice'), response)
+
+    assert result == 120.123
+
+
+def test_wrong_content():
+    data = """
+        The request is answered something unexpected,
+        like an html page or a raw internal stack trace
+    """.strip()
+
+    client = Client('tests/wsdl_files/soap.wsdl')
+    binding = client.service._binding
+
+    response = stub(
+        status_code=200,
+        content=data,
+        encoding='utf-8',
+        headers={}
+    )
+
+    with pytest.raises(TransportError):
+        binding.process_reply(
+            client, binding.get('GetLastTradePrice'), response)
+
+
+def test_wrong_no_unicode_content():
+    data = """
+        The request is answered something unexpected,
+        and the content charset is beyond unicode òñÇÿ
+    """.strip()
+
+    client = Client('tests/wsdl_files/soap.wsdl')
+    binding = client.service._binding
+
+    response = stub(
+        status_code=200,
+        content=data,
+        encoding='utf-8',
+        headers={}
+    )
+
+    with pytest.raises(TransportError):
+        binding.process_reply(
+            client, binding.get('GetLastTradePrice'), response)
+
+
 def test_mime_multipart():
     data = '\r\n'.join(line.strip() for line in """
         --MIME_boundary
@@ -154,6 +251,64 @@ def test_mime_multipart():
     response = stub(
         status_code=200,
         content=data,
+        encoding='utf-8',
+        headers={
+            'Content-Type': 'multipart/related; type="text/xml"; start="<claim061400a.xml at claiming-it.com>"; boundary="MIME_boundary"'
+        }
+    )
+
+    result = binding.process_reply(
+        client, binding.get('GetClaimDetails'), response)
+
+    assert result.root is None
+    assert len(result.attachments) == 2
+
+    assert result.attachments[0].content == b'...Base64 encoded TIFF image...'
+    assert result.attachments[1].content == b'...Raw JPEG image..'
+
+
+def test_mime_multipart_no_encoding():
+    data = '\r\n'.join(line.strip() for line in """
+        --MIME_boundary
+        Content-Type: text/xml
+        Content-Transfer-Encoding: 8bit
+        Content-ID: <claim061400a.xml at claiming-it.com>
+
+        <?xml version='1.0' ?>
+        <SOAP-ENV:Envelope
+        xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
+        <SOAP-ENV:Body>
+        <claim:insurance_claim_auto id="insurance_claim_document_id"
+        xmlns:claim="http://schemas.risky-stuff.com/Auto-Claim">
+        <theSignedForm href="cid:claim061400a.tiff at claiming-it.com"/>
+        <theCrashPhoto href="cid:claim061400a.jpeg at claiming-it.com"/>
+        <!-- ... more claim details go here... -->
+        </claim:insurance_claim_auto>
+        </SOAP-ENV:Body>
+        </SOAP-ENV:Envelope>
+
+        --MIME_boundary
+        Content-Type: image/tiff
+        Content-Transfer-Encoding: base64
+        Content-ID: <claim061400a.tiff at claiming-it.com>
+
+        Li4uQmFzZTY0IGVuY29kZWQgVElGRiBpbWFnZS4uLg==
+
+        --MIME_boundary
+        Content-Type: text/xml
+        Content-ID: <claim061400a.jpeg at claiming-it.com>
+
+        ...Raw JPEG image..
+        --MIME_boundary--
+    """.splitlines()).encode('utf-8')
+
+    client = Client('tests/wsdl_files/claim.wsdl')
+    binding = client.service._binding
+
+    response = stub(
+        status_code=200,
+        content=data,
+        encoding=None,
         headers={
             'Content-Type': 'multipart/related; type="text/xml"; start="<claim061400a.xml at claiming-it.com>"; boundary="MIME_boundary"'
         }
@@ -167,3 +322,38 @@ def test_mime_multipart():
 
     assert result.attachments[0].content == b'...Base64 encoded TIFF image...'
     assert result.attachments[1].content == b'...Raw JPEG image..'
+
+
+def test_unexpected_headers():
+    data = """
+        <?xml version="1.0"?>
+        <soapenv:Envelope
+            xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
+            xmlns:stoc="http://example.com/stockquote.xsd">
+           <soapenv:Header>
+             <stoc:IamUnexpected>uhoh</stoc:IamUnexpected>
+           </soapenv:Header>
+           <soapenv:Body>
+              <stoc:TradePrice>
+                 <price>120.123</price>
+              </stoc:TradePrice>
+           </soapenv:Body>
+        </soapenv:Envelope>
+    """.strip()
+
+    client = Client('tests/wsdl_files/soap_header.wsdl')
+    binding = client.service._binding
+
+    response = stub(
+        status_code=200,
+        content=data,
+        encoding='utf-8',
+        headers={}
+    )
+
+    result = binding.process_reply(
+        client, binding.get('GetLastTradePrice'), response)
+
+    assert result.body.price == 120.123
+    assert result.header.body is None
+    assert len(result.header._raw_elements) == 1
diff --git a/tests/test_xsd.py b/tests/test_xsd.py
index 573784a..ebc99c6 100644
--- a/tests/test_xsd.py
+++ b/tests/test_xsd.py
@@ -149,39 +149,6 @@ def test_invalid_kwarg_simple_type():
         elm(something='is-wrong')
 
 
-def test_group_mixed():
-    custom_type = xsd.Element(
-        etree.QName('http://tests.python-zeep.org/', 'authentication'),
-        xsd.ComplexType(
-            xsd.Sequence([
-                xsd.Element(
-                    etree.QName('http://tests.python-zeep.org/', 'username'),
-                    xsd.String()),
-                xsd.Group(
-                    etree.QName('http://tests.python-zeep.org/', 'groupie'),
-                    xsd.Sequence([
-                        xsd.Element(
-                            etree.QName('http://tests.python-zeep.org/', 'password'),
-                            xsd.String(),
-                        )
-                    ])
-                )
-            ])
-        ))
-    assert custom_type.signature()
-    obj = custom_type(username='foo', password='bar')
-
-    expected = """
-      <document>
-        <ns0:authentication xmlns:ns0="http://tests.python-zeep.org/">
-          <ns0:username>foo</ns0:username>
-          <ns0:password>bar</ns0:password>
-        </ns0:authentication>
-      </document>
-    """
-    node = etree.Element('document')
-    custom_type.render(node, obj)
-    assert_nodes_equal(expected, node)
 
 
 def test_any():
@@ -515,7 +482,7 @@ def test_duplicate_element_names():
         ))
 
     # sequences
-    expected = 'item: xsd:string, item__1: xsd:string, item__2: xsd:string'
+    expected = '{http://tests.python-zeep.org/}container(item: xsd:string, item__1: xsd:string, item__2: xsd:string)'
     assert custom_type.signature() == expected
     obj = custom_type(item='foo', item__1='bar', item__2='lala')
 
@@ -548,7 +515,7 @@ def test_element_attribute_name_conflict():
         ))
 
     # sequences
-    expected = 'item: xsd:string, foo: xsd:string, attr__item: xsd:string'
+    expected = '{http://tests.python-zeep.org/}container(item: xsd:string, foo: xsd:string, attr__item: xsd:string)'
     assert custom_type.signature() == expected
     obj = custom_type(item='foo', foo='x', attr__item='bar')
 
diff --git a/tests/test_xsd_any.py b/tests/test_xsd_any.py
index 1595b2c..3d62b69 100644
--- a/tests/test_xsd_any.py
+++ b/tests/test_xsd_any.py
@@ -3,7 +3,7 @@ import datetime
 import pytest
 from lxml import etree
 
-from tests.utils import assert_nodes_equal, load_xml
+from tests.utils import assert_nodes_equal, load_xml, render_node
 from zeep import xsd
 
 
@@ -26,6 +26,24 @@ def get_any_schema():
     """))
 
 
+
+def test_default_xsd_type():
+    schema = xsd.Schema(load_xml("""
+        <?xml version="1.0"?>
+        <schema xmlns="http://www.w3.org/2001/XMLSchema"
+                xmlns:tns="http://tests.python-zeep.org/"
+                targetNamespace="http://tests.python-zeep.org/"
+                elementFormDefault="qualified">
+          <element name="container"/>
+        </schema>
+    """))
+    assert schema
+
+    container_cls = schema.get_element('ns0:container')
+    data = container_cls()
+    assert data == ''
+
+
 def test_any_simple():
     schema = get_any_schema()
 
@@ -109,6 +127,41 @@ def test_any_value_invalid():
         container_elm.render(node, obj)
 
 
+def test_any_without_element():
+    schema = xsd.Schema(load_xml("""
+        <?xml version="1.0"?>
+        <schema xmlns="http://www.w3.org/2001/XMLSchema"
+                xmlns:tns="http://tests.python-zeep.org/"
+                targetNamespace="http://tests.python-zeep.org/"
+                   elementFormDefault="qualified">
+          <element name="item">
+            <complexType>
+              <sequence>
+                <any minOccurs="0" maxOccurs="1"/>
+              </sequence>
+              <attribute name="type" type="string"/>
+              <attribute name="title" type="string"/>
+            </complexType>
+          </element>
+        </schema>
+    """))
+    item_elm = schema.get_element('{http://tests.python-zeep.org/}item')
+    item = item_elm(xsd.AnyObject(xsd.String(), 'foobar'), type='attr-1', title='attr-2')
+
+    node = render_node(item_elm, item)
+    expected = """
+        <document>
+          <ns0:item xmlns:ns0="http://tests.python-zeep.org/" type="attr-1" title="attr-2">foobar</ns0:item>
+        </document>
+    """
+    assert_nodes_equal(expected, node)
+
+    item = item_elm.parse(node.getchildren()[0], schema)
+    assert item.type == 'attr-1'
+    assert item.title == 'attr-2'
+    assert item._value_1 is None
+
+
 def test_any_with_ref():
     schema = xsd.Schema(load_xml("""
         <?xml version="1.0"?>
@@ -219,6 +272,36 @@ def test_element_any_type():
     item = container_elm.parse(node.getchildren()[0], schema)
     assert item.something == 'bar'
 
+def test_element_any_type_unknown_type():
+    node = etree.fromstring("""
+        <?xml version="1.0"?>
+        <schema xmlns="http://www.w3.org/2001/XMLSchema"
+                xmlns:tns="http://tests.python-zeep.org/"
+                targetNamespace="http://tests.python-zeep.org/"
+                elementFormDefault="qualified">
+          <element name="container">
+            <complexType>
+              <sequence>
+                <element name="something" />
+              </sequence>
+            </complexType>
+          </element>
+        </schema>
+    """.strip())
+    schema = xsd.Schema(node)
+
+    container_elm = schema.get_element('{http://tests.python-zeep.org/}container')
+
+    node = load_xml("""
+        <document xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+            <ns0:container xmlns:ns0="http://tests.python-zeep.org/">
+                <ns0:something xmlns:q1="http://microsoft.com/wsdl/types/" xsi:type="q1:guid">bar</ns0:something>
+            </ns0:container>
+        </document>
+    """)
+    item = container_elm.parse(node.getchildren()[0], schema)
+    assert item.something == 'bar'
+
 
 def test_element_any_type_elements():
     node = etree.fromstring("""
@@ -298,8 +381,8 @@ def test_any_in_nested_sequence():
     """))   # noqa
 
     container_elm = schema.get_element('{http://tests.python-zeep.org/}container')
-    assert container_elm.signature() == (
-        'items: {_value_1: ANY}, version: xsd:string, _value_1: ANY[]')
+    assert container_elm.signature(schema) == (
+        'ns0:container(items: {_value_1: ANY}, version: xsd:string, _value_1: ANY[])')
 
     something = schema.get_element('{http://tests.python-zeep.org/}something')
     foobar = schema.get_element('{http://tests.python-zeep.org/}foobar')
diff --git a/tests/test_xsd_attributes.py b/tests/test_xsd_attributes.py
index f30d76e..535c94a 100644
--- a/tests/test_xsd_attributes.py
+++ b/tests/test_xsd_attributes.py
@@ -26,8 +26,8 @@ def test_anyattribute():
     """))
 
     container_elm = schema.get_element('{http://tests.python-zeep.org/}container')
-    assert container_elm.signature() == (
-        'foo: xsd:string, _attr_1: {}')
+    assert container_elm.signature(schema) == (
+        'ns0:container(foo: xsd:string, _attr_1: {})')
     obj = container_elm(foo='bar', _attr_1=OrderedDict([
         ('hiep', 'hoi'), ('hoi', 'hiep')
     ]))
@@ -73,7 +73,8 @@ def test_attribute_list_type():
     """))
 
     container_elm = schema.get_element('{http://tests.python-zeep.org/}container')
-    assert container_elm.signature() == ('foo: xsd:string, lijst: xsd:int[]')
+    assert container_elm.signature(schema) == (
+        'ns0:container(foo: xsd:string, lijst: xsd:int[])')
     obj = container_elm(foo='bar', lijst=[1, 2, 3])
     expected = """
       <document>
@@ -341,7 +342,8 @@ def test_nested_attribute():
     """))
 
     container_elm = schema.get_element('{http://tests.python-zeep.org/}container')
-    assert container_elm.signature() == 'item: {x: xsd:string, y: xsd:string}'
+    assert container_elm.signature(schema) == (
+        'ns0:container(item: {x: xsd:string, y: xsd:string})')
     obj = container_elm(item={'x': 'foo', 'y': 'bar'})
 
     expected = """
@@ -371,7 +373,7 @@ def test_attribute_union_type():
             <restriction base="xsd:string"/>
           </simpleType>
           <simpleType name="parent">
-            <union memberTypes="one two"/>
+            <union memberTypes="tns:one tns:two"/>
           </simpleType>
           <simpleType name="two">
             <restriction base="xsd:string"/>
diff --git a/tests/test_xsd_builtins.py b/tests/test_xsd_builtins.py
index abbab75..17ae68e 100644
--- a/tests/test_xsd_builtins.py
+++ b/tests/test_xsd_builtins.py
@@ -30,6 +30,8 @@ class TestBoolean:
         assert instance.xmlvalue(False) == 'false'
         assert instance.xmlvalue(1) == 'true'
         assert instance.xmlvalue(0) == 'false'
+        assert instance.xmlvalue('false') == 'false'
+        assert instance.xmlvalue('0') == 'false'
 
     def test_pythonvalue(self):
         instance = builtins.Boolean()
diff --git a/tests/test_xsd_complex_types.py b/tests/test_xsd_complex_types.py
index c22f185..a6d862e 100644
--- a/tests/test_xsd_complex_types.py
+++ b/tests/test_xsd_complex_types.py
@@ -1,11 +1,11 @@
-import pytest
 from lxml import etree
+import pytest
 
 from tests.utils import assert_nodes_equal, load_xml, render_node
 from zeep import xsd
 
 
-def test_single_node():
+def test_xml_xml_single_node():
     schema = xsd.Schema(load_xml("""
         <?xml version="1.0"?>
         <schema xmlns="http://www.w3.org/2001/XMLSchema"
@@ -40,7 +40,7 @@ def test_single_node():
     assert obj.item == 'bar'
 
 
-def test_nested_sequence():
+def test_xml_nested_sequence():
     schema = xsd.Schema(load_xml("""
         <?xml version="1.0"?>
         <schema xmlns="http://www.w3.org/2001/XMLSchema"
@@ -86,7 +86,37 @@ def test_nested_sequence():
     assert obj.item.y == 2
 
 
-def test_single_node_array():
+def test_xml_restriction_self():
+    schema = xsd.Schema(load_xml("""
+        <?xml version="1.0"?>
+        <schema xmlns="http://www.w3.org/2001/XMLSchema"
+                xmlns:tns="http://tests.python-zeep.org/"
+                targetNamespace="http://tests.python-zeep.org/"
+                elementFormDefault="qualified">
+          <element name="container" type="tns:container"/>
+          <complexType name="container">
+            <complexContent>
+              <restriction base="anyType">
+                <sequence>
+                  <element minOccurs="0" maxOccurs="1" name="item">
+                    <complexType>
+                      <sequence>
+                        <element name="child" type="tns:container"/>
+                      </sequence>
+                    </complexType>
+                  </element>
+                </sequence>
+              </restriction>
+            </complexContent>
+          </complexType>
+        </schema>
+    """))
+    schema.set_ns_prefix('tns', 'http://tests.python-zeep.org/')
+    container_elm = schema.get_element('tns:container')
+    container_elm.signature(schema)
+
+
+def test_xml_single_node_array():
     schema = xsd.Schema(load_xml("""
         <?xml version="1.0"?>
         <schema xmlns="http://www.w3.org/2001/XMLSchema"
@@ -124,7 +154,7 @@ def test_single_node_array():
     assert obj.item == ['item-1', 'item-2', 'item-3']
 
 
-def test_single_node_no_iterable():
+def test_xml_single_node_no_iterable():
     schema = xsd.Schema(load_xml("""
         <?xml version="1.0"?>
         <schema xmlns="http://www.w3.org/2001/XMLSchema"
@@ -151,7 +181,7 @@ def test_single_node_no_iterable():
         render_node(container_elm, obj)
 
 
-def test_complex_any_types():
+def test_xml_complex_any_types():
     # see https://github.com/mvantellingen/python-zeep/issues/252
     schema = xsd.Schema(load_xml("""
         <?xml version="1.0"?>
@@ -231,3 +261,37 @@ def test_complex_any_types():
     </document>
     """)  # noqa
     assert_nodes_equal(result, expected)
+
+
+def test_xml_unparsed_elements():
+    schema = xsd.Schema(load_xml("""
+        <?xml version="1.0"?>
+        <schema xmlns="http://www.w3.org/2001/XMLSchema"
+                xmlns:tns="http://tests.python-zeep.org/"
+                targetNamespace="http://tests.python-zeep.org/"
+                elementFormDefault="qualified">
+          <element name="container">
+            <complexType>
+              <sequence>
+                <element minOccurs="0" maxOccurs="1" name="item" type="string" />
+              </sequence>
+            </complexType>
+          </element>
+        </schema>
+    """))
+    schema.strict = False
+    schema.set_ns_prefix('tns', 'http://tests.python-zeep.org/')
+
+    expected = load_xml("""
+      <document>
+        <ns0:container xmlns:ns0="http://tests.python-zeep.org/">
+          <ns0:item>bar</ns0:item>
+          <ns0:idontbelonghere>bar</ns0:idontbelonghere>
+        </ns0:container>
+      </document>
+    """)
+
+    container_elm = schema.get_element('tns:container')
+    obj = container_elm.parse(expected[0], schema)
+    assert obj.item == 'bar'
+    assert obj._raw_elements
diff --git a/tests/test_xsd_extension.py b/tests/test_xsd_extension.py
index 206b5f0..b80faf8 100644
--- a/tests/test_xsd_extension.py
+++ b/tests/test_xsd_extension.py
@@ -123,7 +123,8 @@ def test_complex_content_with_recursive_elements():
         </xsd:schema>
     """))
     pet_type = schema.get_element('{http://tests.python-zeep.org/}Pet')
-    assert(pet_type.signature() == 'name: xsd:string, common_name: xsd:string, children: Pet')
+    assert(pet_type.signature(schema=schema) == 'ns0:Pet(ns0:Pet)')
+    assert(pet_type.type.signature(schema=schema) == 'ns0:Pet(name: xsd:string, common_name: xsd:string, children: ns0:Pet[])')
 
     obj = pet_type(
         name='foo', common_name='bar',
@@ -602,3 +603,100 @@ def test_extension_abstract_complex_type():
         </document>
     """
     assert_nodes_equal(expected, node)
+
+
+def test_extension_base_anytype():
+    schema = xsd.Schema(load_xml("""
+        <?xml version="1.0"?>
+        <xsd:schema
+            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+            xmlns:tns="http://tests.python-zeep.org/"
+            targetNamespace="http://tests.python-zeep.org/"
+            elementFormDefault="qualified">
+
+            <xsd:element name="container">
+              <xsd:complexType>
+                <xsd:complexContent>
+                  <xsd:restriction base="xsd:anyType">
+                    <xsd:attribute name="attr" type="xsd:unsignedInt" use="required"/>
+                    <xsd:anyAttribute namespace="##other" processContents="lax"/>
+                  </xsd:restriction>
+                 </xsd:complexContent>
+              </xsd:complexType>
+            </xsd:element>
+          </xsd:schema>
+    """))
+    container_elm = schema.get_element('{http://tests.python-zeep.org/}container')
+
+    assert container_elm.signature() == (
+        '{http://tests.python-zeep.org/}container(attr: xsd:unsignedInt, _attr_1: {})')
+
+    obj = container_elm(attr='foo')
+
+    node = render_node(container_elm, obj)
+    expected = """
+        <document>
+            <ns0:container xmlns:ns0="http://tests.python-zeep.org/" attr="foo"/>
+        </document>
+    """
+    assert_nodes_equal(expected, node)
+
+
+def test_extension_on_ref():
+    schema = xsd.Schema(load_xml("""
+        <?xml version="1.0"?>
+        <xsd:schema
+            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+            xmlns:tns="http://tests.python-zeep.org/"
+            targetNamespace="http://tests.python-zeep.org/"
+            elementFormDefault="qualified">
+          <xsd:complexType name="type">
+            <xsd:complexContent>
+              <xsd:extension base="tns:base">
+                <xsd:sequence>
+                  <xsd:element ref="tns:extra"/>
+                </xsd:sequence>
+              </xsd:extension>
+            </xsd:complexContent>
+          </xsd:complexType>
+          <xsd:complexType name="base">
+            <xsd:sequence>
+              <xsd:element name="item-1" type="xsd:string"/>
+            </xsd:sequence>
+          </xsd:complexType>
+          <xsd:element name="extra" type="xsd:string"/>
+        </xsd:schema>
+    """))
+
+    type_cls = schema.get_type('ns0:type')
+    assert type_cls.signature()
+
+
+def test_restrict_on_ref():
+    schema = xsd.Schema(load_xml("""
+        <?xml version="1.0"?>
+        <xsd:schema
+            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+            xmlns:tns="http://tests.python-zeep.org/"
+            targetNamespace="http://tests.python-zeep.org/"
+            elementFormDefault="qualified">
+          <xsd:complexType name="type">
+            <xsd:complexContent>
+              <xsd:restriction base="tns:base">
+                <xsd:sequence>
+                  <xsd:element ref="tns:extra"/>
+                </xsd:sequence>
+              </xsd:restriction>
+            </xsd:complexContent>
+          </xsd:complexType>
+          <xsd:complexType name="base">
+            <xsd:sequence>
+              <xsd:element name="item-1" type="xsd:string"/>
+            </xsd:sequence>
+          </xsd:complexType>
+          <xsd:element name="extra" type="xsd:string"/>
+        </xsd:schema>
+    """))
+
+    type_cls = schema.get_type('ns0:type')
+    assert type_cls.signature()
diff --git a/tests/test_xsd_indicators_all.py b/tests/test_xsd_indicators_all.py
new file mode 100644
index 0000000..73b9939
--- /dev/null
+++ b/tests/test_xsd_indicators_all.py
@@ -0,0 +1,65 @@
+from lxml import etree
+
+from tests.utils import assert_nodes_equal, render_node, load_xml
+from zeep import xsd
+
+
+def test_build_occurs_1():
+    custom_type = xsd.Element(
+        etree.QName('http://tests.python-zeep.org/', 'authentication'),
+        xsd.ComplexType(
+            xsd.All([
+                xsd.Element(
+                    etree.QName('http://tests.python-zeep.org/', 'item_1'),
+                    xsd.String()),
+                xsd.Element(
+                    etree.QName('http://tests.python-zeep.org/', 'item_2'),
+                    xsd.String()),
+            ])
+        ))
+
+    obj = custom_type(item_1='foo', item_2='bar')
+    result = render_node(custom_type, obj)
+
+    expected = load_xml("""
+        <document>
+          <ns0:authentication xmlns:ns0="http://tests.python-zeep.org/">
+            <ns0:item_1>foo</ns0:item_1>
+            <ns0:item_2>bar</ns0:item_2>
+          </ns0:authentication>
+        </document>
+    """)
+
+    assert_nodes_equal(result, expected)
+
+    obj = custom_type.parse(result[0], None)
+    assert obj.item_1 == 'foo'
+    assert obj.item_2 == 'bar'
+
+
+def test_build_pare_other_order():
+    custom_type = xsd.Element(
+        etree.QName('http://tests.python-zeep.org/', 'authentication'),
+        xsd.ComplexType(
+            xsd.All([
+                xsd.Element(
+                    etree.QName('http://tests.python-zeep.org/', 'item_1'),
+                    xsd.String()),
+                xsd.Element(
+                    etree.QName('http://tests.python-zeep.org/', 'item_2'),
+                    xsd.String()),
+            ])
+        ))
+
+    xml = load_xml("""
+        <document>
+          <ns0:authentication xmlns:ns0="http://tests.python-zeep.org/">
+            <ns0:item_2>bar</ns0:item_2>
+            <ns0:item_1>foo</ns0:item_1>
+          </ns0:authentication>
+        </document>
+    """)
+
+    obj = custom_type.parse(xml[0], None)
+    assert obj.item_1 == 'foo'
+    assert obj.item_2 == 'bar'
diff --git a/tests/test_xsd_choice.py b/tests/test_xsd_indicators_choice.py
similarity index 73%
rename from tests/test_xsd_choice.py
rename to tests/test_xsd_indicators_choice.py
index cff9296..00b00fb 100644
--- a/tests/test_xsd_choice.py
+++ b/tests/test_xsd_indicators_choice.py
@@ -1,9 +1,10 @@
+from collections import deque
 import pytest
 from lxml import etree
 
 from tests.utils import assert_nodes_equal, load_xml, render_node
 from zeep import xsd
-from zeep.exceptions import XMLParseError
+from zeep.exceptions import XMLParseError, ValidationError
 from zeep.helpers import serialize_object
 
 
@@ -45,7 +46,7 @@ def test_choice_element():
     element.render(node, value)
     assert_nodes_equal(expected, node)
 
-    value = element.parse(node.getchildren()[0], schema)
+    value = element.parse(node[0], schema)
     assert value.item_1 == 'foo'
     assert value.item_2 is None
     assert value.item_3 is None
@@ -89,11 +90,89 @@ def test_choice_element_second_elm():
     element.render(node, value)
     assert_nodes_equal(expected, node)
 
-    value = element.parse(node.getchildren()[0], schema)
+    value = element.parse(node[0], schema)
     assert value.item_1 is None
     assert value.item_2 == 'foo'
     assert value.item_3 is None
 
+def test_choice_element_second_elm_positional():
+    node = etree.fromstring("""
+        <?xml version="1.0"?>
+        <xsd:schema
+                xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+                xmlns:tns="http://tests.python-zeep.org/"
+                elementFormDefault="qualified"
+                targetNamespace="http://tests.python-zeep.org/">
+          <xsd:complexType name="type_1">
+            <xsd:sequence>
+              <xsd:element name="child_1" type="xsd:string"/>
+              <xsd:element name="child_2" type="xsd:string"/>
+            </xsd:sequence>
+          </xsd:complexType>
+          <xsd:complexType name="type_2">
+            <xsd:sequence>
+              <xsd:element name="child_1" type="xsd:string"/>
+              <xsd:element name="child_2" type="xsd:string"/>
+            </xsd:sequence>
+          </xsd:complexType>
+          <xsd:element name="container">
+            <xsd:complexType>
+              <xsd:choice>
+                <xsd:element name="item_1" type="tns:type_1" />
+                <xsd:element name="item_2" type="tns:type_2" />
+              </xsd:choice>
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="containerArray">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:choice>
+                    <xsd:element name="item_1" type="tns:type_1" />
+                    <xsd:element name="item_2" type="tns:type_2" />
+                </xsd:choice>
+              </xsd:sequence>
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:schema>
+    """.strip())
+    schema = xsd.Schema(node)
+
+    child = schema.get_type('ns0:type_2')(child_1='ha', child_2='ho')
+
+    element = schema.get_element('ns0:container')
+    with pytest.raises(TypeError):
+        value = element(child)
+    value = element(item_2=child)
+
+    element = schema.get_element('ns0:containerArray')
+    with pytest.raises(TypeError):
+        value = element(child)
+    value = element(item_2=child)
+
+    element = schema.get_element('ns0:container')
+    value = element(item_2=child)
+    assert value.item_1 is None
+    assert value.item_2 == child
+
+    expected = """
+      <document>
+        <ns0:container xmlns:ns0="http://tests.python-zeep.org/">
+          <ns0:item_2>
+            <ns0:child_1>ha</ns0:child_1>
+            <ns0:child_2>ho</ns0:child_2>
+          </ns0:item_2>
+        </ns0:container>
+      </document>
+    """
+    node = etree.Element('document')
+    element.render(node, value)
+    assert_nodes_equal(expected, node)
+
+    value = element.parse(node[0], schema)
+    assert value.item_1 is None
+    assert value.item_2.child_1 == 'ha'
+    assert value.item_2.child_2 == 'ho'
+
 
 def test_choice_element_multiple():
     node = etree.fromstring("""
@@ -137,7 +216,7 @@ def test_choice_element_multiple():
     element.render(node, value)
     assert_nodes_equal(expected, node)
 
-    value = element.parse(node.getchildren()[0], schema)
+    value = element.parse(node[0], schema)
     assert value._value_1 == [
         {'item_1': 'foo'}, {'item_2': 'bar'}, {'item_1': 'three'},
     ]
@@ -179,6 +258,8 @@ def test_choice_element_optional():
     node = etree.Element('document')
     element.render(node, value)
     assert_nodes_equal(expected, node)
+    value = element.parse(node[0], schema)
+    assert value.item_4 == 'foo'
 
 
 def test_choice_element_with_any():
@@ -219,7 +300,7 @@ def test_choice_element_with_any():
     element.render(node, value)
     assert_nodes_equal(expected, node)
 
-    result = element.parse(node.getchildren()[0], schema)
+    result = element.parse(node[0], schema)
     assert result.name == 'foo'
     assert result.something is True
     assert result.item_1 == 'foo'
@@ -266,7 +347,7 @@ def test_choice_element_with_any_max_occurs():
     """
     node = render_node(element, value)
     assert_nodes_equal(node, expected)
-    result = element.parse(node.getchildren()[0], schema)
+    result = element.parse(node[0], schema)
     assert result.item_2 == 'item-2'
     assert result._value_1 == ['any-content']
 
@@ -319,8 +400,8 @@ def test_choice_in_sequence():
     schema = xsd.Schema(node)
     container_elm = schema.get_element('ns0:container')
 
-    assert container_elm.type.signature() == (
-        'something: xsd:string, ({item_1: xsd:string} | {item_2: xsd:string} | {item_3: xsd:string})')  # noqa
+    assert container_elm.type.signature(schema=schema) == (
+        'ns0:container(something: xsd:string, ({item_1: xsd:string} | {item_2: xsd:string} | {item_3: xsd:string}))')
     value = container_elm(something='foobar', item_1='item-1')
 
     expected = """
@@ -334,6 +415,7 @@ def test_choice_in_sequence():
     node = etree.Element('document')
     container_elm.render(node, value)
     assert_nodes_equal(expected, node)
+    value = container_elm.parse(node[0], schema)
 
 
 def test_choice_with_sequence():
@@ -362,8 +444,8 @@ def test_choice_with_sequence():
     """)
     schema = xsd.Schema(node)
     element = schema.get_element('ns0:container')
-    assert element.type.signature() == (
-        '({item_1: xsd:string, item_2: xsd:string} | {item_3: xsd:string, item_4: xsd:string})')
+    assert element.type.signature(schema=schema) == (
+        'ns0:container(({item_1: xsd:string, item_2: xsd:string} | {item_3: xsd:string, item_4: xsd:string}))')
     value = element(item_1='foo', item_2='bar')
 
     expected = """
@@ -377,6 +459,7 @@ def test_choice_with_sequence():
     node = etree.Element('document')
     element.render(node, value)
     assert_nodes_equal(expected, node)
+    value = element.parse(node[0], schema)
 
 
 def test_choice_with_sequence_once():
@@ -404,8 +487,8 @@ def test_choice_with_sequence_once():
     """)
     schema = xsd.Schema(node)
     element = schema.get_element('ns0:container')
-    assert element.type.signature() == (
-        'item_0: xsd:string, ({item_1: xsd:string, item_2: xsd:string})')
+    assert element.type.signature(schema=schema) == (
+        'ns0:container(item_0: xsd:string, ({item_1: xsd:string, item_2: xsd:string}))')
     value = element(item_0='nul', item_1='foo', item_2='bar')
 
     expected = """
@@ -420,6 +503,94 @@ def test_choice_with_sequence_once():
     node = etree.Element('document')
     element.render(node, value)
     assert_nodes_equal(expected, node)
+    value = element.parse(node[0], schema)
+
+
+def test_choice_with_sequence_unbounded():
+    node = load_xml("""
+        <?xml version="1.0"?>
+        <xsd:schema
+                xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+                xmlns:tns="http://tests.python-zeep.org/"
+                elementFormDefault="qualified"
+                targetNamespace="http://tests.python-zeep.org/">
+          <xsd:element name="container">
+            <xsd:complexType xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+              <xsd:choice>
+                <xsd:sequence maxOccurs="unbounded">
+                  <xsd:element name="item_0" type="xsd:string"/>
+                  <xsd:element name="item_1" type="xsd:string"/>
+                  <xsd:element name="item_2" type="tns:obj"/>
+                </xsd:sequence>
+              </xsd:choice>
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:complexType name="obj">
+            <xsd:sequence maxOccurs="unbounded">
+              <xsd:element name="item_2_1" type="xsd:string"/>
+            </xsd:sequence>
+          </xsd:complexType>
+        </xsd:schema>
+    """)
+    schema = xsd.Schema(node)
+    element = schema.get_element('ns0:container')
+    assert element.type.signature(schema=schema) == (
+        'ns0:container(({[item_0: xsd:string, item_1: xsd:string, item_2: ns0:obj]}))')
+    value = element(_value_1=[
+        {'item_0': 'nul', 'item_1': 'foo', 'item_2': {'_value_1': [{'item_2_1': 'bar'}]}},
+    ])
+
+    expected = """
+      <document>
+        <ns0:container xmlns:ns0="http://tests.python-zeep.org/">
+          <ns0:item_0>nul</ns0:item_0>
+          <ns0:item_1>foo</ns0:item_1>
+          <ns0:item_2>
+            <ns0:item_2_1>bar</ns0:item_2_1>
+          </ns0:item_2>
+        </ns0:container>
+      </document>
+    """
+    node = etree.Element('document')
+    element.render(node, value)
+    assert_nodes_equal(expected, node)
+
+    value = element.parse(node[0], schema)
+    assert value._value_1[0]['item_0'] == 'nul'
+    assert value._value_1[0]['item_1'] == 'foo'
+    assert value._value_1[0]['item_2']._value_1[0]['item_2_1'] == 'bar'
+
+    assert not hasattr(value._value_1[0]['item_2'], 'item_2_1')
+
+
+def test_choice_with_sequence_missing_elements():
+    node = load_xml("""
+        <?xml version="1.0"?>
+        <xsd:schema
+                xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+                xmlns:tns="http://tests.python-zeep.org/"
+                elementFormDefault="qualified"
+                targetNamespace="http://tests.python-zeep.org/">
+          <xsd:element name="container">
+            <xsd:complexType xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+              <xsd:choice maxOccurs="2">
+                <xsd:sequence>
+                    <xsd:element name="item_1" type="xsd:string"/>
+                    <xsd:element name="item_2" type="xsd:string"/>
+                </xsd:sequence>
+              </xsd:choice>
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:schema>
+    """)
+    schema = xsd.Schema(node)
+    element = schema.get_element('ns0:container')
+    assert element.type.signature(schema=schema) == (
+        'ns0:container(({item_1: xsd:string, item_2: xsd:string})[])')
+
+    value = element(_value_1={'item_1': 'foo'})
+    with pytest.raises(ValidationError):
+        render_node(element, value)
 
 
 def test_choice_with_sequence_once_extra_data():
@@ -448,8 +619,8 @@ def test_choice_with_sequence_once_extra_data():
     """)
     schema = xsd.Schema(node)
     element = schema.get_element('ns0:container')
-    assert element.type.signature() == (
-        'item_0: xsd:string, ({item_1: xsd:string, item_2: xsd:string}), item_3: xsd:string')
+    assert element.type.signature(schema=schema) == (
+        'ns0:container(item_0: xsd:string, ({item_1: xsd:string, item_2: xsd:string}), item_3: xsd:string)')
     value = element(item_0='nul', item_1='foo', item_2='bar', item_3='item-3')
 
     expected = """
@@ -465,6 +636,7 @@ def test_choice_with_sequence_once_extra_data():
     node = etree.Element('document')
     element.render(node, value)
     assert_nodes_equal(expected, node)
+    value = element.parse(node[0], schema)
 
 
 def test_choice_with_sequence_second():
@@ -493,8 +665,8 @@ def test_choice_with_sequence_second():
     """)
     schema = xsd.Schema(node)
     element = schema.get_element('ns0:container')
-    assert element.type.signature() == (
-        '({item_1: xsd:string, item_2: xsd:string} | {item_3: xsd:string, item_4: xsd:string})')
+    assert element.type.signature(schema=schema) == (
+        'ns0:container(({item_1: xsd:string, item_2: xsd:string} | {item_3: xsd:string, item_4: xsd:string}))')
     value = element(item_3='foo', item_4='bar')
 
     expected = """
@@ -508,6 +680,7 @@ def test_choice_with_sequence_second():
     node = etree.Element('document')
     element.render(node, value)
     assert_nodes_equal(expected, node)
+    value = element.parse(node[0], schema)
 
 
 def test_choice_with_sequence_invalid():
@@ -536,8 +709,8 @@ def test_choice_with_sequence_invalid():
     """)
     schema = xsd.Schema(node)
     element = schema.get_element('ns0:container')
-    assert element.type.signature() == (
-        '({item_1: xsd:string, item_2: xsd:string} | {item_3: xsd:string, item_4: xsd:string})')
+    assert element.type.signature(schema=schema) == (
+        'ns0:container(({item_1: xsd:string, item_2: xsd:string} | {item_3: xsd:string, item_4: xsd:string}))')
 
     with pytest.raises(TypeError):
         element(item_1='foo', item_4='bar')
@@ -594,6 +767,7 @@ def test_choice_with_sequence_change():
     node = etree.Element('document')
     element.render(node, elm)
     assert_nodes_equal(expected, node)
+    value = element.parse(node[0], schema)
 
 
 def test_choice_with_sequence_change_named():
@@ -638,6 +812,7 @@ def test_choice_with_sequence_change_named():
     node = etree.Element('document')
     element.render(node, elm)
     assert_nodes_equal(expected, node)
+    value = element.parse(node[0], schema)
 
 
 def test_choice_with_sequence_multiple():
@@ -666,8 +841,8 @@ def test_choice_with_sequence_multiple():
     """)
     schema = xsd.Schema(node)
     element = schema.get_element('ns0:container')
-    assert element.type.signature() == (
-        '({item_1: xsd:string, item_2: xsd:string} | {item_3: xsd:string, item_4: xsd:string})[]')
+    assert element.type.signature(schema=schema) == (
+        'ns0:container(({item_1: xsd:string, item_2: xsd:string} | {item_3: xsd:string, item_4: xsd:string})[])')
     value = element(_value_1=[
         dict(item_1='foo', item_2='bar'),
         dict(item_3='foo', item_4='bar'),
@@ -686,6 +861,7 @@ def test_choice_with_sequence_multiple():
     node = etree.Element('document')
     element.render(node, value)
     assert_nodes_equal(expected, node)
+    value = element.parse(node[0], schema)
 
 
 def test_choice_with_sequence_and_element():
@@ -713,8 +889,8 @@ def test_choice_with_sequence_and_element():
     """)
     schema = xsd.Schema(node)
     element = schema.get_element('ns0:container')
-    assert element.type.signature() == (
-        '({item_1: xsd:string} | {({item_2: xsd:string} | {item_3: xsd:string})})')
+    assert element.type.signature(schema=schema) == (
+        'ns0:container(({item_1: xsd:string} | {({item_2: xsd:string} | {item_3: xsd:string})}))')
 
     value = element(item_2='foo')
 
@@ -728,6 +904,7 @@ def test_choice_with_sequence_and_element():
     node = etree.Element('document')
     element.render(node, value)
     assert_nodes_equal(expected, node)
+    value = element.parse(node[0], schema)
 
 
 def test_element_ref_in_choice():
@@ -1043,3 +1220,138 @@ def test_choice_extend():
     assert value['item-1-2'] == 'bar'
     assert value['_value_1'][0] == {'item-2-1': 'xafoo'}
     assert value['_value_1'][1] == {'item-2-2': 'xabar'}
+
+
+def test_nested_choice():
+    schema = xsd.Schema(load_xml("""
+            <?xml version="1.0"?>
+            <schema xmlns="http://www.w3.org/2001/XMLSchema"
+                    xmlns:tns="http://tests.python-zeep.org/"
+                    targetNamespace="http://tests.python-zeep.org/"
+                    elementFormDefault="qualified">
+                <element name="container">
+                    <complexType>
+                        <sequence>
+                            <choice>
+                                <choice minOccurs="2" maxOccurs="unbounded">
+                                    <element ref="tns:a" />
+                                </choice>
+                                <element ref="tns:b" />
+                            </choice>
+                        </sequence>
+                    </complexType>
+                </element>
+                <element name="a" type="string" />
+                <element name="b" type="string" />
+            </schema>
+        """))
+
+    schema.set_ns_prefix('tns', 'http://tests.python-zeep.org/')
+    container_type = schema.get_element('tns:container')
+
+    item = container_type(_value_1=[{'a': 'item-1'}, {'a': 'item-2'}])
+    assert item._value_1[0] == {'a': 'item-1'}
+    assert item._value_1[1] == {'a': 'item-2'}
+
+    expected = load_xml("""
+        <document>
+           <ns0:container xmlns:ns0="http://tests.python-zeep.org/">
+               <ns0:a>item-1</ns0:a>
+               <ns0:a>item-2</ns0:a>
+           </ns0:container>
+        </document>
+       """)
+    node = render_node(container_type, item)
+    assert_nodes_equal(node, expected)
+
+    result = container_type.parse(expected[0], schema)
+    assert result._value_1[0] == {'a': 'item-1'}
+    assert result._value_1[1] == {'a': 'item-2'}
+
+    expected = load_xml("""
+        <container xmlns="http://tests.python-zeep.org/">
+          <b>1</b>
+        </container>
+   """)
+
+    result = container_type.parse(expected, schema)
+    assert result.b == '1'
+
+
+def test_unit_choice_parse_xmlelements_max_1():
+    schema = xsd.Schema(load_xml("""
+        <?xml version="1.0"?>
+        <xsd:schema
+                xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+                xmlns:tns="http://tests.python-zeep.org/"
+                elementFormDefault="qualified"
+                targetNamespace="http://tests.python-zeep.org/">
+          <xsd:element name="container">
+            <xsd:complexType>
+              <xsd:choice>
+                <xsd:element name="item_1" type="xsd:string" />
+                <xsd:element name="item_2" type="xsd:string" />
+                <xsd:element name="item_3" type="xsd:string" />
+              </xsd:choice>
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:schema>
+    """))
+    element = schema.get_element('ns0:container')
+
+    def create_elm(name, text):
+        elm = etree.Element(name)
+        elm.text = text
+        return elm
+
+    data = deque([
+        create_elm('item_1', 'item-1'),
+        create_elm('item_2', 'item-2'),
+        create_elm('item_1', 'item-3'),
+    ])
+
+    result = element.type._element.parse_xmlelements(data, schema)
+    assert result == {'item_1': 'item-1'}
+    assert len(data) == 2
+
+
+def test_unit_choice_parse_xmlelements_max_2():
+    schema = xsd.Schema(load_xml("""
+        <?xml version="1.0"?>
+        <xsd:schema
+                xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+                xmlns:tns="http://tests.python-zeep.org/"
+                elementFormDefault="qualified"
+                targetNamespace="http://tests.python-zeep.org/">
+          <xsd:element name="container">
+            <xsd:complexType>
+              <xsd:choice maxOccurs="2">
+                <xsd:element name="item_1" type="xsd:string" />
+                <xsd:element name="item_2" type="xsd:string" />
+                <xsd:element name="item_3" type="xsd:string" />
+              </xsd:choice>
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:schema>
+    """))
+    element = schema.get_element('ns0:container')
+
+    def create_elm(name, text):
+        elm = etree.Element(name)
+        elm.text = text
+        return elm
+
+    data = deque([
+        create_elm('item_1', 'item-1'),
+        create_elm('item_2', 'item-2'),
+        create_elm('item_1', 'item-3'),
+    ])
+
+    result = element.type._element.parse_xmlelements(data, schema, name='items')
+    assert result == {
+        'items': [
+            {'item_1': 'item-1'},
+            {'item_2': 'item-2'},
+        ]
+    }
+    assert len(data) == 1
diff --git a/tests/test_xsd_indicators_group.py b/tests/test_xsd_indicators_group.py
new file mode 100644
index 0000000..68d5924
--- /dev/null
+++ b/tests/test_xsd_indicators_group.py
@@ -0,0 +1,417 @@
+import pytest
+from lxml import etree
+
+from tests.utils import assert_nodes_equal, render_node, load_xml
+from zeep import xsd
+
+
+def test_build_objects():
+    custom_type = xsd.Element(
+        etree.QName('http://tests.python-zeep.org/', 'authentication'),
+        xsd.ComplexType(
+            xsd.Sequence([
+                xsd.Element(
+                    etree.QName('http://tests.python-zeep.org/', 'username'),
+                    xsd.String()),
+                xsd.Group(
+                    etree.QName('http://tests.python-zeep.org/', 'groupie'),
+                    xsd.Sequence([
+                        xsd.Element(
+                            etree.QName('http://tests.python-zeep.org/', 'password'),
+                            xsd.String(),
+                        )
+                    ])
+                )
+            ])
+        ))
+    assert custom_type.signature()
+    obj = custom_type(username='foo', password='bar')
+
+    expected = """
+      <document>
+        <ns0:authentication xmlns:ns0="http://tests.python-zeep.org/">
+          <ns0:username>foo</ns0:username>
+          <ns0:password>bar</ns0:password>
+        </ns0:authentication>
+      </document>
+    """
+    node = etree.Element('document')
+    custom_type.render(node, obj)
+    assert_nodes_equal(expected, node)
+
+    obj = custom_type.parse(node[0], None)
+    assert obj.username == 'foo'
+    assert obj.password == 'bar'
+
+
+def test_build_group_min_occurs_1():
+    custom_type = xsd.Element(
+        etree.QName('http://tests.python-zeep.org/', 'authentication'),
+        xsd.ComplexType(
+            xsd.Group(
+                etree.QName('http://tests.python-zeep.org/', 'foobar'),
+                xsd.Sequence([
+                    xsd.Element(
+                        etree.QName('http://tests.python-zeep.org/', 'item_1'),
+                        xsd.String()),
+                    xsd.Element(
+                        etree.QName('http://tests.python-zeep.org/', 'item_2'),
+                        xsd.String()),
+                ]),
+                min_occurs=1)
+        ))
+
+    obj = custom_type(item_1='foo', item_2='bar')
+    assert obj.item_1 == 'foo'
+    assert obj.item_2 == 'bar'
+
+    result = render_node(custom_type, obj)
+    expected = load_xml("""
+        <document>
+          <ns0:authentication xmlns:ns0="http://tests.python-zeep.org/">
+            <ns0:item_1>foo</ns0:item_1>
+            <ns0:item_2>bar</ns0:item_2>
+          </ns0:authentication>
+        </document>
+    """)
+
+    assert_nodes_equal(result, expected)
+
+    obj = custom_type.parse(result[0], None)
+    assert obj.item_1 == 'foo'
+    assert obj.item_2 == 'bar'
+    assert not hasattr(obj, 'foobar')
+
+
+def test_build_group_min_occurs_1_parse_args():
+    custom_type = xsd.Element(
+        etree.QName('http://tests.python-zeep.org/', 'authentication'),
+        xsd.ComplexType(
+            xsd.Group(
+                etree.QName('http://tests.python-zeep.org/', 'foobar'),
+                xsd.Sequence([
+                    xsd.Element(
+                        etree.QName('http://tests.python-zeep.org/', 'item_1'),
+                        xsd.String()),
+                    xsd.Element(
+                        etree.QName('http://tests.python-zeep.org/', 'item_2'),
+                        xsd.String()),
+                ]),
+                min_occurs=1)
+        ))
+
+    obj = custom_type('foo', 'bar')
+    assert obj.item_1 == 'foo'
+    assert obj.item_2 == 'bar'
+
+
+def test_build_group_min_occurs_2():
+    custom_type = xsd.Element(
+        etree.QName('http://tests.python-zeep.org/', 'authentication'),
+        xsd.ComplexType(
+            xsd.Group(
+                etree.QName('http://tests.python-zeep.org/', 'foobar'),
+                xsd.Sequence([
+                    xsd.Element(
+                        etree.QName('http://tests.python-zeep.org/', 'item_1'),
+                        xsd.String()),
+                    xsd.Element(
+                        etree.QName('http://tests.python-zeep.org/', 'item_2'),
+                        xsd.String()),
+                ]),
+                min_occurs=2, max_occurs=2)
+        ))
+
+    obj = custom_type(_value_1=[
+        {'item_1': 'foo', 'item_2': 'bar'},
+        {'item_1': 'foo', 'item_2': 'bar'},
+    ])
+    assert obj._value_1 == [
+        {'item_1': 'foo', 'item_2': 'bar'},
+        {'item_1': 'foo', 'item_2': 'bar'},
+    ]
+
+    result = render_node(custom_type, obj)
+    expected = load_xml("""
+        <document>
+          <ns0:authentication xmlns:ns0="http://tests.python-zeep.org/">
+            <ns0:item_1>foo</ns0:item_1>
+            <ns0:item_2>bar</ns0:item_2>
+            <ns0:item_1>foo</ns0:item_1>
+            <ns0:item_2>bar</ns0:item_2>
+          </ns0:authentication>
+        </document>
+    """)
+
+    assert_nodes_equal(result, expected)
+
+    obj = custom_type.parse(result[0], None)
+    assert obj._value_1 == [
+        {'item_1': 'foo', 'item_2': 'bar'},
+        {'item_1': 'foo', 'item_2': 'bar'},
+    ]
+    assert not hasattr(obj, 'foobar')
+
+
+def test_build_group_min_occurs_2_sequence_min_occurs_2():
+    custom_type = xsd.Element(
+        etree.QName('http://tests.python-zeep.org/', 'authentication'),
+        xsd.ComplexType(
+            xsd.Group(
+                etree.QName('http://tests.python-zeep.org/', 'foobar'),
+                xsd.Sequence([
+                    xsd.Element(
+                        etree.QName('http://tests.python-zeep.org/', 'item_1'),
+                        xsd.String()),
+                    xsd.Element(
+                        etree.QName('http://tests.python-zeep.org/', 'item_2'),
+                        xsd.String()),
+                ], min_occurs=2, max_occurs=2),
+                min_occurs=2, max_occurs=2)
+        ))
+    expected = etree.fromstring("""
+        <ns0:container xmlns:ns0="http://tests.python-zeep.org/">
+          <ns0:item_1>foo</ns0:item_1>
+          <ns0:item_2>bar</ns0:item_2>
+          <ns0:item_1>foo</ns0:item_1>
+          <ns0:item_2>bar</ns0:item_2>
+          <ns0:item_1>foo</ns0:item_1>
+          <ns0:item_2>bar</ns0:item_2>
+          <ns0:item_1>foo</ns0:item_1>
+          <ns0:item_2>bar</ns0:item_2>
+        </ns0:container>
+    """)
+    obj = custom_type.parse(expected, None)
+    assert obj._value_1 == [
+        {'_value_1': [
+            {'item_1': 'foo', 'item_2': 'bar'},
+            {'item_1': 'foo', 'item_2': 'bar'},
+        ]},
+        {'_value_1': [
+            {'item_1': 'foo', 'item_2': 'bar'},
+            {'item_1': 'foo', 'item_2': 'bar'},
+        ]},
+    ]
+    assert not hasattr(obj, 'foobar')
+
+
+def test_build_group_occurs_1_invalid_kwarg():
+    custom_type = xsd.Element(
+        etree.QName('http://tests.python-zeep.org/', 'authentication'),
+        xsd.ComplexType(
+            xsd.Group(
+                etree.QName('http://tests.python-zeep.org/', 'foobar'),
+                xsd.Sequence([
+                    xsd.Element(
+                        etree.QName('http://tests.python-zeep.org/', 'item_1'),
+                        xsd.String()),
+                    xsd.Element(
+                        etree.QName('http://tests.python-zeep.org/', 'item_2'),
+                        xsd.String()),
+                ]),
+                min_occurs=1, max_occurs=1)
+        ))
+
+    with pytest.raises(TypeError):
+        custom_type(item_1='foo', item_2='bar', error=True)
+
+
+def test_build_group_min_occurs_2_invalid_kwarg():
+    custom_type = xsd.Element(
+        etree.QName('http://tests.python-zeep.org/', 'authentication'),
+        xsd.ComplexType(
+            xsd.Group(
+                etree.QName('http://tests.python-zeep.org/', 'foobar'),
+                xsd.Sequence([
+                    xsd.Element(
+                        etree.QName('http://tests.python-zeep.org/', 'item_1'),
+                        xsd.String()),
+                    xsd.Element(
+                        etree.QName('http://tests.python-zeep.org/', 'item_2'),
+                        xsd.String()),
+                ]),
+                min_occurs=2, max_occurs=2)
+        ))
+
+    with pytest.raises(TypeError):
+        custom_type(_value_1=[
+            {'item_1': 'foo', 'item_2': 'bar', 'error': True},
+            {'item_1': 'foo', 'item_2': 'bar'},
+        ])
+
+
+def test_xml_group_via_ref():
+    schema = xsd.Schema(load_xml("""
+        <?xml version="1.0"?>
+        <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
+                   xmlns:tns="http://tests.python-zeep.org/"
+                   targetNamespace="http://tests.python-zeep.org/"
+                   elementFormDefault="qualified">
+
+          <xs:element name="Address">
+            <xs:complexType>
+              <xs:group ref="tns:Name" />
+            </xs:complexType>
+          </xs:element>
+
+          <xs:group name="Name">
+            <xs:sequence>
+              <xs:element name="first_name" type="xs:string" />
+              <xs:element name="last_name" type="xs:string" />
+            </xs:sequence>
+          </xs:group>
+
+        </xs:schema>
+    """))
+    address_type = schema.get_element('{http://tests.python-zeep.org/}Address')
+
+    obj = address_type(first_name='foo', last_name='bar')
+
+    node = etree.Element('document')
+    address_type.render(node, obj)
+    expected = """
+        <document>
+            <ns0:Address xmlns:ns0="http://tests.python-zeep.org/">
+                <ns0:first_name>foo</ns0:first_name>
+                <ns0:last_name>bar</ns0:last_name>
+            </ns0:Address>
+        </document>
+    """
+    assert_nodes_equal(expected, node)
+
+
+def test_xml_group_via_ref_max_occurs_unbounded():
+    schema = xsd.Schema(load_xml("""
+        <?xml version="1.0"?>
+        <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
+                   xmlns:tns="http://tests.python-zeep.org/"
+                   targetNamespace="http://tests.python-zeep.org/"
+                   elementFormDefault="qualified">
+
+          <xs:element name="Address">
+            <xs:complexType>
+              <xs:group ref="tns:Name" minOccurs="0" maxOccurs="unbounded"/>
+            </xs:complexType>
+          </xs:element>
+
+          <xs:group name="Name">
+            <xs:sequence>
+              <xs:element name="first_name" type="xs:string" />
+              <xs:element name="last_name" type="xs:string" />
+            </xs:sequence>
+          </xs:group>
+
+        </xs:schema>
+    """))
+    address_type = schema.get_element('{http://tests.python-zeep.org/}Address')
+
+    obj = address_type(
+        _value_1=[
+            {'first_name': 'foo-1', 'last_name': 'bar-1'},
+            {'first_name': 'foo-2', 'last_name': 'bar-2'},
+        ])
+
+    node = etree.Element('document')
+    address_type.render(node, obj)
+    expected = """
+        <document>
+            <ns0:Address xmlns:ns0="http://tests.python-zeep.org/">
+                <ns0:first_name>foo-1</ns0:first_name>
+                <ns0:last_name>bar-1</ns0:last_name>
+                <ns0:first_name>foo-2</ns0:first_name>
+                <ns0:last_name>bar-2</ns0:last_name>
+            </ns0:Address>
+        </document>
+    """
+    assert_nodes_equal(expected, node)
+
+    obj = address_type.parse(node[0], None)
+    assert obj._value_1[0]['first_name'] == 'foo-1'
+    assert obj._value_1[0]['last_name'] == 'bar-1'
+    assert obj._value_1[1]['first_name'] == 'foo-2'
+    assert obj._value_1[1]['last_name'] == 'bar-2'
+
+
+def test_xml_multiple_groups_in_sequence():
+    schema = xsd.Schema(load_xml("""
+        <?xml version="1.0"?>
+        <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
+                   xmlns:tns="http://tests.python-zeep.org/"
+                   targetNamespace="http://tests.python-zeep.org/"
+                   elementFormDefault="unqualified">
+
+          <xs:element name="Address" type="tns:AddressType" />
+
+          <xs:complexType name="AddressType">
+            <xs:sequence>
+              <xs:group ref="tns:NameGroup"/>
+              <xs:group ref="tns:AddressGroup"/>
+            </xs:sequence>
+          </xs:complexType>
+
+          <xs:group name="NameGroup">
+            <xs:sequence>
+              <xs:element name="first_name" type="xs:string" />
+              <xs:element name="last_name" type="xs:string" />
+            </xs:sequence>
+          </xs:group>
+
+          <xs:group name="AddressGroup">
+            <xs:annotation>
+              <xs:documentation>blub</xs:documentation>
+            </xs:annotation>
+            <xs:sequence>
+              <xs:element name="city" type="xs:string" />
+              <xs:element name="country" type="xs:string" />
+            </xs:sequence>
+          </xs:group>
+        </xs:schema>
+    """))
+    address_type = schema.get_element('{http://tests.python-zeep.org/}Address')
+
+    obj = address_type(
+        first_name='foo', last_name='bar',
+        city='Utrecht', country='The Netherlands')
+
+    node = etree.Element('document')
+    address_type.render(node, obj)
+    expected = """
+        <document>
+            <ns0:Address xmlns:ns0="http://tests.python-zeep.org/">
+                <first_name>foo</first_name>
+                <last_name>bar</last_name>
+                <city>Utrecht</city>
+                <country>The Netherlands</country>
+            </ns0:Address>
+        </document>
+    """
+    assert_nodes_equal(expected, node)
+
+
+def test_xml_group_methods():
+    schema = xsd.Schema(load_xml("""
+        <?xml version="1.0"?>
+        <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
+                   xmlns:tns="http://tests.python-zeep.org/"
+                   targetNamespace="http://tests.python-zeep.org/"
+                   elementFormDefault="unqualified">
+
+          <xs:group name="Group">
+            <xs:annotation>
+              <xs:documentation>blub</xs:documentation>
+            </xs:annotation>
+            <xs:sequence>
+              <xs:element name="city" type="xs:string" />
+              <xs:element name="country" type="xs:string" />
+            </xs:sequence>
+          </xs:group>
+
+        </xs:schema>
+    """))
+    Group = schema.get_group('{http://tests.python-zeep.org/}Group')
+    assert Group.signature(schema) == (
+        'ns0:Group(city: xsd:string, country: xsd:string)')
+    assert str(Group) == (
+        '{http://tests.python-zeep.org/}Group(city: xsd:string, country: xsd:string)')
+
+    assert len(list(Group)) == 2
diff --git a/tests/test_xsd_indicators_sequence.py b/tests/test_xsd_indicators_sequence.py
new file mode 100644
index 0000000..63b7c7b
--- /dev/null
+++ b/tests/test_xsd_indicators_sequence.py
@@ -0,0 +1,477 @@
+import pytest
+from lxml import etree
+
+from tests.utils import load_xml, render_node, assert_nodes_equal
+from zeep import xsd
+
+
+def test_build_occurs_1():
+    custom_type = xsd.Element(
+        etree.QName('http://tests.python-zeep.org/', 'authentication'),
+        xsd.ComplexType(
+            xsd.Sequence([
+                xsd.Element(
+                    etree.QName('http://tests.python-zeep.org/', 'item_1'),
+                    xsd.String()),
+                xsd.Element(
+                    etree.QName('http://tests.python-zeep.org/', 'item_2'),
+                    xsd.String()),
+            ])
+        ))
+    obj = custom_type(item_1='foo', item_2='bar')
+    assert obj.item_1 == 'foo'
+    assert obj.item_2 == 'bar'
+
+    result = render_node(custom_type, obj)
+    expected = load_xml("""
+        <document>
+          <ns0:authentication xmlns:ns0="http://tests.python-zeep.org/">
+            <ns0:item_1>foo</ns0:item_1>
+            <ns0:item_2>bar</ns0:item_2>
+          </ns0:authentication>
+        </document>
+    """)
+    assert_nodes_equal(result, expected)
+    obj = custom_type.parse(expected[0], None)
+    assert obj.item_1 == 'foo'
+    assert obj.item_2 == 'bar'
+
+
+def test_build_occurs_1_skip_value():
+    custom_type = xsd.Element(
+        etree.QName('http://tests.python-zeep.org/', 'authentication'),
+        xsd.ComplexType(
+            xsd.Sequence([
+                xsd.Element(
+                    etree.QName('http://tests.python-zeep.org/', 'item_1'),
+                    xsd.String()),
+                xsd.Element(
+                    etree.QName('http://tests.python-zeep.org/', 'item_2'),
+                    xsd.String()),
+            ])
+        ))
+    obj = custom_type(item_1=xsd.SkipValue, item_2='bar')
+    assert obj.item_1 == xsd.SkipValue
+    assert obj.item_2 == 'bar'
+
+    result = render_node(custom_type, obj)
+    expected = load_xml("""
+        <document>
+          <ns0:authentication xmlns:ns0="http://tests.python-zeep.org/">
+            <ns0:item_2>bar</ns0:item_2>
+          </ns0:authentication>
+        </document>
+    """)
+    assert_nodes_equal(result, expected)
+
+
+def test_build_min_occurs_2_max_occurs_2():
+    custom_type = xsd.Element(
+        etree.QName('http://tests.python-zeep.org/', 'authentication'),
+        xsd.ComplexType(
+            xsd.Sequence([
+                xsd.Element(
+                    etree.QName('http://tests.python-zeep.org/', 'item_1'),
+                    xsd.String()),
+                xsd.Element(
+                    etree.QName('http://tests.python-zeep.org/', 'item_2'),
+                    xsd.String()),
+            ], min_occurs=2, max_occurs=2)
+        ))
+
+    assert custom_type.signature()
+
+
+    elm = custom_type(_value_1=[
+        {'item_1': 'foo-1', 'item_2': 'bar-1'},
+        {'item_1': 'foo-2', 'item_2': 'bar-2'},
+    ])
+
+    assert elm._value_1 == [
+        {'item_1': 'foo-1', 'item_2': 'bar-1'},
+        {'item_1': 'foo-2', 'item_2': 'bar-2'},
+    ]
+
+    expected = load_xml("""
+        <ns0:container xmlns:ns0="http://tests.python-zeep.org/">
+          <ns0:item_1>foo</ns0:item_1>
+          <ns0:item_2>bar</ns0:item_2>
+          <ns0:item_1>foo</ns0:item_1>
+          <ns0:item_2>bar</ns0:item_2>
+        </ns0:container>
+    """)
+    obj = custom_type.parse(expected, None)
+    assert obj._value_1 == [
+        {
+            'item_1': 'foo',
+            'item_2': 'bar',
+        },
+        {
+            'item_1': 'foo',
+            'item_2': 'bar',
+        },
+    ]
+
+
+def test_build_min_occurs_2_max_occurs_2_error():
+    custom_type = xsd.Element(
+        etree.QName('http://tests.python-zeep.org/', 'authentication'),
+        xsd.ComplexType(
+            xsd.Sequence([
+                xsd.Element(
+                    etree.QName('http://tests.python-zeep.org/', 'item_1'),
+                    xsd.String()),
+                xsd.Element(
+                    etree.QName('http://tests.python-zeep.org/', 'item_2'),
+                    xsd.String()),
+            ], min_occurs=2, max_occurs=2)
+        ))
+
+    with pytest.raises(TypeError):
+        custom_type(_value_1={
+            'item_1': 'foo-1', 'item_2': 'bar-1', 'error': True
+        })
+
+
+def test_build_sequence_and_attributes():
+    custom_element = xsd.Element(
+        etree.QName('http://tests.python-zeep.org/', 'authentication'),
+        xsd.ComplexType(
+            xsd.Sequence([
+                xsd.Element(
+                    etree.QName('http://tests.python-zeep.org/', 'item_1'),
+                    xsd.String()),
+                xsd.Element(
+                    etree.QName('http://tests.python-zeep.org/', 'item_2'),
+                    xsd.String()),
+            ]),
+            [
+                xsd.Attribute(
+                    etree.QName('http://tests.python-zeep.org/', 'attr_1'),
+                    xsd.String()),
+                xsd.Attribute('attr_2', xsd.String()),
+            ]
+        ))
+    expected = load_xml("""
+        <ns0:authentication xmlns:ns0="http://tests.python-zeep.org/" ns0:attr_1="x" attr_2="y">
+          <ns0:item_1>foo</ns0:item_1>
+          <ns0:item_2>bar</ns0:item_2>
+        </ns0:authentication>
+    """)
+    obj = custom_element.parse(expected, None)
+    assert obj.item_1 == 'foo'
+    assert obj.item_2 == 'bar'
+    assert obj.attr_1 == 'x'
+    assert obj.attr_2 == 'y'
+
+
+def test_build_sequence_with_optional_elements():
+    custom_type = xsd.Element(
+        etree.QName('http://tests.python-zeep.org/', 'container'),
+        xsd.ComplexType(
+            xsd.Sequence([
+                xsd.Element(
+                    etree.QName('http://tests.python-zeep.org/', 'item_1'),
+                    xsd.String()),
+                xsd.Element(
+                    etree.QName('http://tests.python-zeep.org/', 'item_2'),
+                    xsd.ComplexType(
+                        xsd.Sequence([
+                            xsd.Element(
+                                etree.QName('http://tests.python-zeep.org/', 'item_2_1'),
+                                xsd.String(),
+                                nillable=True)
+                        ])
+                    )
+                ),
+                xsd.Element(
+                    etree.QName('http://tests.python-zeep.org/', 'item_3'),
+                    xsd.String(),
+                    max_occurs=2),
+                xsd.Element(
+                    etree.QName('http://tests.python-zeep.org/', 'item_4'),
+                    xsd.String(),
+                    min_occurs=0),
+            ])
+        ))
+    expected = etree.fromstring("""
+        <ns0:container xmlns:ns0="http://tests.python-zeep.org/">
+          <ns0:item_1>1</ns0:item_1>
+          <ns0:item_2/>
+          <ns0:item_3>3</ns0:item_3>
+        </ns0:container>
+    """)
+    obj = custom_type.parse(expected, None)
+    assert obj.item_1 == '1'
+    assert obj.item_2 is None
+    assert obj.item_3 == ['3']
+    assert obj.item_4 is None
+
+
+def test_build_max_occurs_unbounded():
+    custom_type = xsd.Element(
+        etree.QName('http://tests.python-zeep.org/', 'authentication'),
+        xsd.ComplexType(
+            xsd.Sequence([
+                xsd.Element(
+                    etree.QName('http://tests.python-zeep.org/', 'item_1'),
+                    xsd.String()),
+                xsd.Element(
+                    etree.QName('http://tests.python-zeep.org/', 'item_2'),
+                    xsd.String()),
+            ], max_occurs='unbounded')
+        ))
+    expected = etree.fromstring("""
+        <ns0:container xmlns:ns0="http://tests.python-zeep.org/">
+          <ns0:item_1>foo</ns0:item_1>
+          <ns0:item_2>bar</ns0:item_2>
+        </ns0:container>
+    """)
+    obj = custom_type.parse(expected, None)
+    assert obj._value_1 == [
+        {
+            'item_1': 'foo',
+            'item_2': 'bar',
+        }
+    ]
+
+
+def test_xml_sequence_with_choice():
+    schema = xsd.Schema(load_xml("""
+        <schema
+            xmlns="http://www.w3.org/2001/XMLSchema"
+            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+            xmlns:tns="http://tests.python-zeep.org/tst"
+            elementFormDefault="qualified"
+            targetNamespace="http://tests.python-zeep.org/tst">
+          <element name="container">
+            <complexType>
+              <sequence>
+                <choice>
+                  <element name="item_1" type="xsd:string" />
+                  <element name="item_2" type="xsd:string" />
+                </choice>
+                <element name="item_3" type="xsd:string" />
+              </sequence>
+            </complexType>
+          </element>
+        </schema>
+    """))
+
+    xml = load_xml("""
+        <tst:container
+            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xmlns:tst="http://tests.python-zeep.org/tst">
+          <tst:item_1>blabla</tst:item_1>
+          <tst:item_3>haha</tst:item_3>
+        </tst:container>
+    """)
+
+    elm = schema.get_element('{http://tests.python-zeep.org/tst}container')
+    result = elm.parse(xml, schema)
+    assert result.item_1 == 'blabla'
+    assert result.item_3 == 'haha'
+
+
+def test_xml_sequence_with_choice_max_occurs_2():
+    schema = xsd.Schema(load_xml("""
+        <schema
+            xmlns="http://www.w3.org/2001/XMLSchema"
+            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+            xmlns:tns="http://tests.python-zeep.org/tst"
+            elementFormDefault="qualified"
+            targetNamespace="http://tests.python-zeep.org/tst">
+          <element name="container">
+            <complexType>
+              <sequence>
+                <choice maxOccurs="2">
+                  <element name="item_1" type="xsd:string" />
+                  <element name="item_2" type="xsd:string" />
+                </choice>
+                <element name="item_3" type="xsd:string" />
+              </sequence>
+              <attribute name="item_1" type="xsd:string" use="optional" />
+              <attribute name="item_2" type="xsd:string" use="optional" />
+            </complexType>
+          </element>
+        </schema>
+    """))
+
+    xml = load_xml("""
+        <tst:container
+            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xmlns:tst="http://tests.python-zeep.org/tst">
+          <tst:item_1>item-1-1</tst:item_1>
+          <tst:item_1>item-1-2</tst:item_1>
+          <tst:item_3>item-3</tst:item_3>
+        </tst:container>
+    """)
+
+    elm = schema.get_element('{http://tests.python-zeep.org/tst}container')
+    result = elm.parse(xml, schema)
+    assert result._value_1 == [
+        {'item_1': 'item-1-1'},
+        {'item_1': 'item-1-2'},
+    ]
+
+    assert result.item_3 == 'item-3'
+
+
+def test_xml_sequence_with_choice_max_occurs_3():
+    schema = xsd.Schema(load_xml("""
+        <schema
+            xmlns="http://www.w3.org/2001/XMLSchema"
+            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+            xmlns:tns="http://tests.python-zeep.org/tst"
+            elementFormDefault="qualified"
+            targetNamespace="http://tests.python-zeep.org/tst">
+          <element name="container">
+            <complexType>
+              <sequence>
+                <choice maxOccurs="3">
+                  <sequence>
+                    <element name="item_1" type="xsd:string" />
+                    <element name="item_2" type="xsd:string" />
+                  </sequence>
+                  <element name="item_3" type="xsd:string" />
+                </choice>
+                <element name="item_4" type="xsd:string" />
+              </sequence>
+            </complexType>
+          </element>
+        </schema>
+    """))
+
+    xml = load_xml("""
+        <tst:container
+            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xmlns:tst="http://tests.python-zeep.org/tst">
+          <tst:item_1>text-1</tst:item_1>
+          <tst:item_2>text-2</tst:item_2>
+          <tst:item_1>text-1</tst:item_1>
+          <tst:item_2>text-2</tst:item_2>
+          <tst:item_3>text-3</tst:item_3>
+          <tst:item_4>text-4</tst:item_4>
+        </tst:container>
+    """)
+
+    elm = schema.get_element('{http://tests.python-zeep.org/tst}container')
+    result = elm.parse(xml, schema)
+    assert result._value_1 == [
+        {'item_1': 'text-1', 'item_2': 'text-2'},
+        {'item_1': 'text-1', 'item_2': 'text-2'},
+        {'item_3': 'text-3'},
+    ]
+    assert result.item_4 == 'text-4'
+
+
+def test_xml_sequence_with_nil_element():
+    schema = xsd.Schema(load_xml("""
+        <schema
+            xmlns="http://www.w3.org/2001/XMLSchema"
+            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+            xmlns:tns="http://tests.python-zeep.org/"
+            elementFormDefault="qualified"
+            targetNamespace="http://tests.python-zeep.org/">
+          <element name="container">
+            <complexType>
+              <sequence>
+                <element name="item" type="xsd:string" maxOccurs="unbounded"/>
+              </sequence>
+            </complexType>
+          </element>
+        </schema>
+    """))
+
+    xml = load_xml("""
+        <tns:container
+            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xmlns:tns="http://tests.python-zeep.org/">
+          <tns:item>text-1</tns:item>
+          <tns:item>text-2</tns:item>
+          <tns:item/>
+          <tns:item>text-4</tns:item>
+          <tns:item>text-5</tns:item>
+        </tns:container>
+    """)
+
+    elm = schema.get_element('{http://tests.python-zeep.org/}container')
+    result = elm.parse(xml, schema)
+    assert result.item == [
+        'text-1',
+        'text-2',
+        None,
+        'text-4',
+        'text-5',
+    ]
+
+
+def test_xml_sequence_unbounded():
+    schema = xsd.Schema(load_xml("""
+        <schema
+            xmlns="http://www.w3.org/2001/XMLSchema"
+            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+            xmlns:tns="http://tests.python-zeep.org/"
+            elementFormDefault="qualified"
+            targetNamespace="http://tests.python-zeep.org/">
+          <complexType name="ValueListType">
+            <sequence maxOccurs="unbounded" minOccurs="0">
+              <element ref="tns:Value"/>
+            </sequence>
+          </complexType>
+          <element name="ValueList" type="tns:ValueListType"/>
+          <element name="Value" type="tns:LongName"/>
+          <simpleType name="LongName">
+            <restriction base="string">
+              <maxLength value="256"/>
+           </restriction>
+          </simpleType>
+        </schema>
+    """))
+
+    elm_type = schema.get_type('{http://tests.python-zeep.org/}ValueListType')
+
+    with pytest.raises(TypeError):
+        elm_type(Value='bla')
+    elm_type(_value_1={'Value': 'bla'})
+
+
+def test_xml_sequence_recover_from_missing_element():
+    schema = xsd.Schema(load_xml("""
+        <schema
+            xmlns="http://www.w3.org/2001/XMLSchema"
+            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+            xmlns:tns="http://tests.python-zeep.org/"
+            elementFormDefault="qualified"
+            targetNamespace="http://tests.python-zeep.org/">
+          <complexType name="container">
+            <sequence>
+              <element name="item_1" type="xsd:string"/>
+              <element name="item_2" type="xsd:string"/>
+              <element name="item_3" type="xsd:string"/>
+              <element name="item_4" type="xsd:string"/>
+            </sequence>
+          </complexType>
+        </schema>
+    """), strict=False)
+
+    xml = load_xml("""
+        <tns:container
+            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            xmlns:tns="http://tests.python-zeep.org/">
+          <tns:item_1>text-1</tns:item_1>
+          <tns:item_3>text-3</tns:item_3>
+          <tns:item_4>text-4</tns:item_4>
+        </tns:container>
+    """)
+    elm_type = schema.get_type('{http://tests.python-zeep.org/}container')
+    result = elm_type.parse_xmlelement(xml, schema)
+    assert result.item_1 == 'text-1'
+    assert result.item_2 is None
+    assert result.item_3 == 'text-3'
+    assert result.item_4 == 'text-4'
diff --git a/tests/test_xsd_integration.py b/tests/test_xsd_integration.py
index b262b48..04d796f 100644
--- a/tests/test_xsd_integration.py
+++ b/tests/test_xsd_integration.py
@@ -3,11 +3,11 @@ import copy
 import pytest
 from lxml import etree
 
-from tests.utils import assert_nodes_equal, load_xml
+from tests.utils import assert_nodes_equal, load_xml, render_node
 from zeep import xsd
 
 
-def test_complex_type_nested_wrong_type():
+def test_xml_complex_type_nested_wrong_type():
     schema = xsd.Schema(load_xml("""
         <?xml version="1.0"?>
         <schema xmlns="http://www.w3.org/2001/XMLSchema"
@@ -36,7 +36,7 @@ def test_complex_type_nested_wrong_type():
         container_elm(item={'bar': 1})
 
 
-def test_element_with_annotation():
+def test_xml_element_with_annotation():
     schema = xsd.Schema(load_xml("""
         <?xml version="1.0"?>
         <schema xmlns="http://www.w3.org/2001/XMLSchema"
@@ -61,7 +61,7 @@ def test_element_with_annotation():
     address_type(foo='bar')
 
 
-def test_complex_type_parsexml():
+def test_xml_complex_type_parsexml():
     schema = xsd.Schema(load_xml("""
         <?xml version="1.0"?>
         <schema xmlns="http://www.w3.org/2001/XMLSchema"
@@ -90,8 +90,8 @@ def test_complex_type_parsexml():
     assert obj.foo == 'bar'
 
 
-def test_array():
-    node = etree.fromstring("""
+def test_xml_array():
+    schema = xsd.Schema(load_xml("""
         <?xml version="1.0"?>
         <schema xmlns="http://www.w3.org/2001/XMLSchema"
                 xmlns:tns="http://tests.python-zeep.org/"
@@ -105,9 +105,7 @@ def test_array():
             </complexType>
           </element>
         </schema>
-    """.strip())
-
-    schema = xsd.Schema(node)
+    """))
     schema.set_ns_prefix('tns', 'http://tests.python-zeep.org/')
 
     address_type = schema.get_element('tns:Address')
@@ -130,8 +128,8 @@ def test_array():
     assert_nodes_equal(expected, node)
 
 
-def test_complex_type_unbounded_one():
-    node = etree.fromstring("""
+def test_xml_complex_type_unbounded_one():
+    schema = xsd.Schema(load_xml("""
         <?xml version="1.0"?>
         <schema xmlns="http://www.w3.org/2001/XMLSchema"
                 xmlns:tns="http://tests.python-zeep.org/"
@@ -145,9 +143,7 @@ def test_complex_type_unbounded_one():
             </complexType>
           </element>
         </schema>
-    """.strip())
-
-    schema = xsd.Schema(node)
+    """))
     address_type = schema.get_element('{http://tests.python-zeep.org/}Address')
     obj = address_type(foo=['foo'])
 
@@ -164,8 +160,8 @@ def test_complex_type_unbounded_one():
     assert_nodes_equal(expected, node)
 
 
-def test_complex_type_unbounded_named():
-    node = etree.fromstring("""
+def test_xml_complex_type_unbounded_named():
+    schema = xsd.Schema(load_xml("""
         <?xml version="1.0"?>
         <schema xmlns="http://www.w3.org/2001/XMLSchema"
                 xmlns:tns="http://tests.python-zeep.org/"
@@ -178,9 +174,8 @@ def test_complex_type_unbounded_named():
             </sequence>
           </complexType>
         </schema>
-    """.strip())
+    """))
 
-    schema = xsd.Schema(node)
     address_type = schema.get_element('{http://tests.python-zeep.org/}Address')
     obj = address_type()
     assert obj.foo == []
@@ -201,8 +196,8 @@ def test_complex_type_unbounded_named():
     assert_nodes_equal(expected, node)
 
 
-def test_complex_type_array_to_other_complex_object():
-    node = etree.fromstring("""
+def test_xml_complex_type_array_to_other_complex_object():
+    schema = xsd.Schema(load_xml("""
         <?xml version="1.0"?>
         <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
           <xs:complexType name="Address">
@@ -217,9 +212,8 @@ def test_complex_type_array_to_other_complex_object():
           </xs:complexType>
           <xs:element name="ArrayOfAddress" type="ArrayOfAddress"/>
         </xs:schema>
-    """.strip())  # noqa
+    """))
 
-    schema = xsd.Schema(node)
     address_array = schema.get_element('ArrayOfAddress')
     obj = address_array()
     assert obj.Address == []
@@ -227,21 +221,26 @@ def test_complex_type_array_to_other_complex_object():
     obj.Address.append(schema.get_type('Address')(foo='foo'))
     obj.Address.append(schema.get_type('Address')(foo='bar'))
 
-    node = etree.fromstring("""
+    expected = """
         <?xml version="1.0"?>
-        <ArrayOfAddress>
-            <Address>
-                <foo>foo</foo>
-            </Address>
-            <Address>
-                <foo>bar</foo>
-            </Address>
-        </ArrayOfAddress>
-    """.strip())
+        <document>
+            <ArrayOfAddress>
+                <Address>
+                    <foo>foo</foo>
+                </Address>
+                <Address>
+                    <foo>bar</foo>
+                </Address>
+            </ArrayOfAddress>
+        </document>
+    """
+
+    result = render_node(address_array, obj)
+    assert_nodes_equal(expected, result)
 
 
-def test_complex_type_init_kwargs():
-    node = etree.fromstring("""
+def test_xml_complex_type_init_kwargs():
+    schema = xsd.Schema(load_xml("""
         <?xml version="1.0"?>
         <schema xmlns="http://www.w3.org/2001/XMLSchema"
                 xmlns:tns="http://tests.python-zeep.org/"
@@ -256,9 +255,8 @@ def test_complex_type_init_kwargs():
             </complexType>
           </element>
         </schema>
-    """.strip())
+    """))
 
-    schema = xsd.Schema(node)
     address_type = schema.get_element('{http://tests.python-zeep.org/}Address')
     obj = address_type(
         NameFirst='John', NameLast='Doe', Email='j.doe at example.com')
@@ -267,8 +265,8 @@ def test_complex_type_init_kwargs():
     assert obj.Email == 'j.doe at example.com'
 
 
-def test_complex_type_init_args():
-    node = etree.fromstring("""
+def test_xml_complex_type_init_args():
+    schema = xsd.Schema(load_xml("""
         <?xml version="1.0"?>
         <schema xmlns="http://www.w3.org/2001/XMLSchema"
                 xmlns:tns="http://tests.python-zeep.org/"
@@ -283,9 +281,7 @@ def test_complex_type_init_args():
             </complexType>
           </element>
         </schema>
-    """.strip())
-
-    schema = xsd.Schema(node)
+    """))
     address_type = schema.get_element('{http://tests.python-zeep.org/}Address')
     obj = address_type('John', 'Doe', 'j.doe at example.com')
     assert obj.NameFirst == 'John'
@@ -293,107 +289,9 @@ def test_complex_type_init_args():
     assert obj.Email == 'j.doe at example.com'
 
 
-def test_group():
-    node = etree.fromstring("""
-        <?xml version="1.0"?>
-        <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
-                   xmlns:tns="http://tests.python-zeep.org/"
-                   targetNamespace="http://tests.python-zeep.org/"
-                   elementFormDefault="qualified">
-
-          <xs:element name="Address">
-            <xs:complexType>
-              <xs:group ref="tns:Name" />
-            </xs:complexType>
-          </xs:element>
-
-          <xs:group name="Name">
-            <xs:sequence>
-              <xs:element name="first_name" type="xs:string" />
-              <xs:element name="last_name" type="xs:string" />
-            </xs:sequence>
-          </xs:group>
-
-        </xs:schema>
-    """.strip())
-    schema = xsd.Schema(node)
-    address_type = schema.get_element('{http://tests.python-zeep.org/}Address')
-
-    obj = address_type(first_name='foo', last_name='bar')
-
-    node = etree.Element('document')
-    address_type.render(node, obj)
-    expected = """
-        <document>
-            <ns0:Address xmlns:ns0="http://tests.python-zeep.org/">
-                <ns0:first_name>foo</ns0:first_name>
-                <ns0:last_name>bar</ns0:last_name>
-            </ns0:Address>
-        </document>
-    """
-    assert_nodes_equal(expected, node)
-
-
-def test_group_for_type():
-    node = etree.fromstring("""
-        <?xml version="1.0"?>
-        <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
-                   xmlns:tns="http://tests.python-zeep.org/"
-                   targetNamespace="http://tests.python-zeep.org/"
-                   elementFormDefault="unqualified">
-
-          <xs:element name="Address" type="tns:AddressType" />
-
-          <xs:complexType name="AddressType">
-            <xs:sequence>
-              <xs:group ref="tns:NameGroup"/>
-              <xs:group ref="tns:AddressGroup"/>
-            </xs:sequence>
-          </xs:complexType>
-
-          <xs:group name="NameGroup">
-            <xs:sequence>
-              <xs:element name="first_name" type="xs:string" />
-              <xs:element name="last_name" type="xs:string" />
-            </xs:sequence>
-          </xs:group>
-
-          <xs:group name="AddressGroup">
-            <xs:annotation>
-              <xs:documentation>blub</xs:documentation>
-            </xs:annotation>
-            <xs:sequence>
-              <xs:element name="city" type="xs:string" />
-              <xs:element name="country" type="xs:string" />
-            </xs:sequence>
-          </xs:group>
-        </xs:schema>
-    """.strip())
-    schema = xsd.Schema(node)
-    address_type = schema.get_element('{http://tests.python-zeep.org/}Address')
-
-    obj = address_type(
-        first_name='foo', last_name='bar',
-        city='Utrecht', country='The Netherlands')
-
-    node = etree.Element('document')
-    address_type.render(node, obj)
-    expected = """
-        <document>
-            <ns0:Address xmlns:ns0="http://tests.python-zeep.org/">
-                <first_name>foo</first_name>
-                <last_name>bar</last_name>
-                <city>Utrecht</city>
-                <country>The Netherlands</country>
-            </ns0:Address>
-        </document>
-    """
-    assert_nodes_equal(expected, node)
-
-
-def test_element_ref_missing_namespace():
+def test_xml_element_ref_missing_namespace():
     # For buggy soap servers (#170)
-    node = etree.fromstring("""
+    schema = xsd.Schema(load_xml("""
         <?xml version="1.0"?>
         <schema xmlns="http://www.w3.org/2001/XMLSchema"
                 xmlns:tns="http://tests.python-zeep.org/"
@@ -407,9 +305,7 @@ def test_element_ref_missing_namespace():
             </complexType>
           </element>
         </schema>
-    """.strip())
-
-    schema = xsd.Schema(node)
+    """))
 
     custom_type = schema.get_element('{http://tests.python-zeep.org/}bar')
     input_xml = load_xml("""
@@ -421,8 +317,8 @@ def test_element_ref_missing_namespace():
     assert item.foo == 'bar'
 
 
-def test_element_ref():
-    node = etree.fromstring("""
+def test_xml_element_ref():
+    schema = xsd.Schema(load_xml("""
         <?xml version="1.0"?>
         <schema xmlns="http://www.w3.org/2001/XMLSchema"
                 xmlns:tns="http://tests.python-zeep.org/"
@@ -437,9 +333,7 @@ def test_element_ref():
             </complexType>
           </element>
         </schema>
-    """.strip())
-
-    schema = xsd.Schema(node)
+    """))
 
     foo_type = schema.get_element('{http://tests.python-zeep.org/}foo')
     assert isinstance(foo_type.type, xsd.String)
@@ -460,8 +354,8 @@ def test_element_ref():
     assert_nodes_equal(expected, node)
 
 
-def test_element_ref_occurs():
-    node = etree.fromstring("""
+def test_xml_element_ref_occurs():
+    schema = xsd.Schema(load_xml("""
         <?xml version="1.0"?>
         <schema xmlns="http://www.w3.org/2001/XMLSchema"
                 xmlns:tns="http://tests.python-zeep.org/"
@@ -477,9 +371,7 @@ def test_element_ref_occurs():
             </complexType>
           </element>
         </schema>
-    """.strip())
-
-    schema = xsd.Schema(node)
+    """))
 
     foo_type = schema.get_element('{http://tests.python-zeep.org/}foo')
     assert isinstance(foo_type.type, xsd.String)
@@ -500,8 +392,8 @@ def test_element_ref_occurs():
     assert_nodes_equal(expected, node)
 
 
-def test_unqualified():
-    node = etree.fromstring("""
+def test_xml_unqualified():
+    schema = xsd.Schema(load_xml("""
         <?xml version="1.0"?>
         <schema xmlns="http://www.w3.org/2001/XMLSchema"
                 xmlns:xsd="http://www.w3.org/2001/XMLSchema"
@@ -517,9 +409,8 @@ def test_unqualified():
             </complexType>
           </element>
         </schema>
-    """.strip())
+    """))
 
-    schema = xsd.Schema(node)
     address_type = schema.get_element('{http://tests.python-zeep.org/}Address')
     obj = address_type(foo='bar')
 
@@ -536,8 +427,8 @@ def test_unqualified():
     assert_nodes_equal(expected, node)
 
 
-def test_defaults():
-    node = etree.fromstring("""
+def test_xml_defaults():
+    schema = xsd.Schema(load_xml("""
         <?xml version="1.0"?>
         <xsd:schema
                 xmlns:xsd="http://www.w3.org/2001/XMLSchema"
@@ -547,25 +438,26 @@ def test_defaults():
           <xsd:element name="container">
             <xsd:complexType>
               <xsd:sequence>
-                <xsd:element name="foo" type="xsd:string" default="hoi"/>
+                <xsd:element name="item_1" type="xsd:string" default="hoi"/>
+                <xsd:element name="item_2" type="xsd:string" default="hoi" minOccurs="0"/>
               </xsd:sequence>
-              <xsd:attribute name="bar" type="xsd:string" default="hoi"/>
+              <xsd:attribute name="attr_1" type="xsd:string" default="hoi"/>
             </xsd:complexType>
           </xsd:element>
         </xsd:schema>
-    """.strip())
+    """))
 
-    schema = xsd.Schema(node)
     container_type = schema.get_element(
         '{http://tests.python-zeep.org/}container')
     obj = container_type()
-    assert obj.foo == "hoi"
-    assert obj.bar == "hoi"
+    assert obj.item_1 == "hoi"
+    assert obj.item_2 is None
+    assert obj.attr_1 == "hoi"
 
     expected = """
       <document>
-        <ns0:container xmlns:ns0="http://tests.python-zeep.org/" bar="hoi">
-          <ns0:foo>hoi</ns0:foo>
+        <ns0:container xmlns:ns0="http://tests.python-zeep.org/" attr_1="hoi">
+          <ns0:item_1>hoi</ns0:item_1>
         </ns0:container>
       </document>
     """
@@ -573,9 +465,23 @@ def test_defaults():
     container_type.render(node, obj)
     assert_nodes_equal(expected, node)
 
+    obj.item_2 = 'ok'
 
-def test_defaults_parse():
-    node = etree.fromstring("""
+    expected = """
+      <document>
+        <ns0:container xmlns:ns0="http://tests.python-zeep.org/" attr_1="hoi">
+          <ns0:item_1>hoi</ns0:item_1>
+          <ns0:item_2>ok</ns0:item_2>
+        </ns0:container>
+      </document>
+    """
+    node = etree.Element('document')
+    container_type.render(node, obj)
+    assert_nodes_equal(expected, node)
+
+
+def test_xml_defaults_parse_boolean():
+    schema = xsd.Schema(load_xml("""
         <?xml version="1.0"?>
         <xsd:schema
                 xmlns:xsd="http://www.w3.org/2001/XMLSchema"
@@ -585,29 +491,65 @@ def test_defaults_parse():
           <xsd:element name="container">
             <xsd:complexType>
               <xsd:sequence>
-                <xsd:element name="foo" type="xsd:string" default="hoi" minOccurs="0"/>
+                <xsd:element name="foo" type="xsd:boolean" default="false"/>
               </xsd:sequence>
-              <xsd:attribute name="bar" type="xsd:string" default="hoi"/>
+              <xsd:attribute name="bar" type="xsd:boolean" default="0"/>
             </xsd:complexType>
           </xsd:element>
         </xsd:schema>
-    """.strip())
+    """))
+
+    container_type = schema.get_element(
+        '{http://tests.python-zeep.org/}container')
+    obj = container_type()
+    assert obj.foo == "false"
+    assert obj.bar == "0"
+
+    expected = """
+      <document>
+        <ns0:container xmlns:ns0="http://tests.python-zeep.org/" bar="false">
+          <ns0:foo>false</ns0:foo>
+        </ns0:container>
+      </document>
+    """
+    node = etree.Element('document')
+    container_type.render(node, obj)
+    assert_nodes_equal(expected, node)
+
+
+def test_xml_defaults_parse():
+    schema = xsd.Schema(load_xml("""
+        <?xml version="1.0"?>
+        <xsd:schema
+                xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+                xmlns:tns="http://tests.python-zeep.org/"
+                elementFormDefault="qualified"
+                targetNamespace="http://tests.python-zeep.org/">
+          <xsd:element name="container">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="item_1" type="xsd:string" default="hoi" minOccurs="0"/>
+              </xsd:sequence>
+              <xsd:attribute name="attr_1" type="xsd:string" default="hoi"/>
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:schema>
+    """))
 
-    schema = xsd.Schema(node)
     container_elm = schema.get_element(
         '{http://tests.python-zeep.org/}container')
 
     node = load_xml("""
         <ns0:container xmlns:ns0="http://tests.python-zeep.org/">
-          <ns0:foo>hoi</ns0:foo>
+          <ns0:item_1>hoi</ns0:item_1>
         </ns0:container>
     """)
     item = container_elm.parse(node, schema)
-    assert item.bar == 'hoi'
+    assert item.attr_1 == 'hoi'
 
 
-def test_init_with_dicts():
-    node = etree.fromstring("""
+def test_xml_init_with_dicts():
+    schema = xsd.Schema(load_xml("""
         <?xml version="1.0"?>
         <xsd:schema
                 xmlns:xsd="http://www.w3.org/2001/XMLSchema"
@@ -638,9 +580,8 @@ def test_init_with_dicts():
             </xsd:sequence>
           </xsd:complexType>
         </xsd:schema>
-    """.strip())
+    """))
 
-    schema = xsd.Schema(node)
     address_type = schema.get_element('{http://tests.python-zeep.org/}Address')
     obj = address_type(name='foo', container={'service': [{'name': 'foo'}]})
 
@@ -662,9 +603,8 @@ def test_init_with_dicts():
     assert_nodes_equal(expected, node)
 
 
-
-def test_sequence_in_sequence():
-    node = load_xml("""
+def test_xml_sequence_in_sequence():
+    schema = xsd.Schema(load_xml("""
         <?xml version="1.0"?>
         <schema
                 xmlns="http://www.w3.org/2001/XMLSchema"
@@ -684,8 +624,7 @@ def test_sequence_in_sequence():
           </element>
           <element name="foobar" type="xsd:string"/>
         </schema>
-    """)
-    schema = xsd.Schema(node)
+    """))
     element = schema.get_element('ns0:container')
     value = element(item_1="foo", item_2="bar")
 
@@ -703,7 +642,7 @@ def test_sequence_in_sequence():
     assert_nodes_equal(expected, node)
 
 
-def test_sequence_in_sequence_many():
+def test_xml_sequence_in_sequence_many():
     node = load_xml("""
         <?xml version="1.0"?>
         <schema
@@ -753,8 +692,8 @@ def test_sequence_in_sequence_many():
     assert_nodes_equal(expected, node)
 
 
-def test_complex_type_empty():
-    node = etree.fromstring("""
+def test_xml_complex_type_empty():
+    schema = xsd.Schema(load_xml("""
         <?xml version="1.0"?>
         <schema xmlns="http://www.w3.org/2001/XMLSchema"
                 xmlns:tns="http://tests.python-zeep.org/"
@@ -769,9 +708,7 @@ def test_complex_type_empty():
             </complexType>
           </element>
         </schema>
-    """.strip())
-
-    schema = xsd.Schema(node)
+    """))
 
     container_elm = schema.get_element('{http://tests.python-zeep.org/}container')
     obj = container_elm(something={})
@@ -790,7 +727,7 @@ def test_complex_type_empty():
     assert item.something is None
 
 
-def test_schema_as_payload():
+def test_xml_schema_as_payload():
     schema = xsd.Schema(load_xml("""
         <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                 xmlns:tns="http://tests.python-zeep.org/"
@@ -834,7 +771,7 @@ def test_schema_as_payload():
     assert value._value_1['item-2'] == 'value-2'
 
 
-def test_nill():
+def test_xml_nill():
     schema = xsd.Schema(load_xml("""
         <?xml version="1.0"?>
         <schema xmlns="http://www.w3.org/2001/XMLSchema"
@@ -868,8 +805,8 @@ def test_nill():
     assert_nodes_equal(expected, node)
 
 
-def test_empty_xmlns():
-    node = load_xml("""
+def test_xml_empty_xmlns():
+    schema = xsd.Schema(load_xml("""
         <?xml version="1.0"?>
         <schema xmlns="http://www.w3.org/2001/XMLSchema"
                 xmlns:tns="http://tests.python-zeep.org/"
@@ -885,9 +822,7 @@ def test_empty_xmlns():
             </complexType>
           </element>
         </schema>
-    """.strip())
-
-    schema = xsd.Schema(node)
+    """))
 
     container_elm = schema.get_element('{http://tests.python-zeep.org/}container')
     node = load_xml("""
@@ -905,8 +840,8 @@ def test_empty_xmlns():
     assert item._value_1 == 'foo'
 
 
-def test_keep_objects_intact():
-    node = etree.fromstring("""
+def test_xml_keep_objects_intact():
+    schema = xsd.Schema(load_xml("""
         <?xml version="1.0"?>
         <xsd:schema
                 xmlns:xsd="http://www.w3.org/2001/XMLSchema"
@@ -937,9 +872,8 @@ def test_keep_objects_intact():
             </xsd:sequence>
           </xsd:complexType>
         </xsd:schema>
-    """.strip())
+    """))
 
-    schema = xsd.Schema(node)
     address_type = schema.get_element('{http://tests.python-zeep.org/}Address')
     obj = address_type(name='foo', container={'service': [{'name': 'foo'}]})
 
diff --git a/tests/test_xsd_parse.py b/tests/test_xsd_parse.py
index aa0e76c..511d019 100644
--- a/tests/test_xsd_parse.py
+++ b/tests/test_xsd_parse.py
@@ -7,132 +7,6 @@ from zeep import xsd
 from zeep.xsd.schema import Schema
 
 
-def test_sequence_parse_basic():
-    custom_type = xsd.Element(
-        etree.QName('http://tests.python-zeep.org/', 'authentication'),
-        xsd.ComplexType(
-            xsd.Sequence([
-                xsd.Element(
-                    etree.QName('http://tests.python-zeep.org/', 'item_1'),
-                    xsd.String()),
-                xsd.Element(
-                    etree.QName('http://tests.python-zeep.org/', 'item_2'),
-                    xsd.String()),
-            ])
-        ))
-    expected = etree.fromstring("""
-        <ns0:container xmlns:ns0="http://tests.python-zeep.org/">
-          <ns0:item_1>foo</ns0:item_1>
-          <ns0:item_2>bar</ns0:item_2>
-        </ns0:container>
-    """)
-    obj = custom_type.parse(expected, None)
-    assert obj.item_1 == 'foo'
-    assert obj.item_2 == 'bar'
-
-
-def test_sequence_parse_max_occurs_infinite_loop():
-    custom_type = xsd.Element(
-        etree.QName('http://tests.python-zeep.org/', 'authentication'),
-        xsd.ComplexType(
-            xsd.Sequence([
-                xsd.Element(
-                    etree.QName('http://tests.python-zeep.org/', 'item_1'),
-                    xsd.String()),
-                xsd.Element(
-                    etree.QName('http://tests.python-zeep.org/', 'item_2'),
-                    xsd.String()),
-            ], max_occurs='unbounded')
-        ))
-    expected = etree.fromstring("""
-        <ns0:container xmlns:ns0="http://tests.python-zeep.org/">
-          <ns0:item_1>foo</ns0:item_1>
-          <ns0:item_2>bar</ns0:item_2>
-        </ns0:container>
-    """)
-    obj = custom_type.parse(expected, None)
-    assert obj._value_1 == [
-        {
-            'item_1': 'foo',
-            'item_2': 'bar',
-        }
-    ]
-
-def test_sequence_parse_basic_with_attrs():
-    custom_element = xsd.Element(
-        etree.QName('http://tests.python-zeep.org/', 'authentication'),
-        xsd.ComplexType(
-            xsd.Sequence([
-                xsd.Element(
-                    etree.QName('http://tests.python-zeep.org/', 'item_1'),
-                    xsd.String()),
-                xsd.Element(
-                    etree.QName('http://tests.python-zeep.org/', 'item_2'),
-                    xsd.String()),
-            ]),
-            [
-                xsd.Attribute(
-                    etree.QName('http://tests.python-zeep.org/', 'attr_1'),
-                    xsd.String()),
-                xsd.Attribute('attr_2', xsd.String()),
-            ]
-        ))
-    expected = etree.fromstring("""
-        <ns0:authentication xmlns:ns0="http://tests.python-zeep.org/" ns0:attr_1="x" attr_2="y">
-          <ns0:item_1>foo</ns0:item_1>
-          <ns0:item_2>bar</ns0:item_2>
-        </ns0:authentication>
-    """)
-    obj = custom_element.parse(expected, None)
-    assert obj.item_1 == 'foo'
-    assert obj.item_2 == 'bar'
-    assert obj.attr_1 == 'x'
-    assert obj.attr_2 == 'y'
-
-
-def test_sequence_parse_with_optional():
-    custom_type = xsd.Element(
-        etree.QName('http://tests.python-zeep.org/', 'container'),
-        xsd.ComplexType(
-            xsd.Sequence([
-                xsd.Element(
-                    etree.QName('http://tests.python-zeep.org/', 'item_1'),
-                    xsd.String()),
-                xsd.Element(
-                    etree.QName('http://tests.python-zeep.org/', 'item_2'),
-                    xsd.ComplexType(
-                        xsd.Sequence([
-                            xsd.Element(
-                                etree.QName('http://tests.python-zeep.org/', 'item_2_1'),
-                                xsd.String(),
-                                nillable=True)
-                        ])
-                    )
-                ),
-                xsd.Element(
-                    etree.QName('http://tests.python-zeep.org/', 'item_3'),
-                    xsd.String(),
-                    max_occurs=2),
-                xsd.Element(
-                    etree.QName('http://tests.python-zeep.org/', 'item_4'),
-                    xsd.String(),
-                    min_occurs=0),
-            ])
-        ))
-    expected = etree.fromstring("""
-        <ns0:container xmlns:ns0="http://tests.python-zeep.org/">
-          <ns0:item_1>1</ns0:item_1>
-          <ns0:item_2/>
-          <ns0:item_3>3</ns0:item_3>
-        </ns0:container>
-    """)
-    obj = custom_type.parse(expected, None)
-    assert obj.item_1 == '1'
-    assert obj.item_2 is None
-    assert obj.item_3 == ['3']
-    assert obj.item_4 is None
-
-
 def test_sequence_parse_regression():
     schema_doc = load_xml(b"""
         <?xml version="1.0" encoding="utf-8"?>
@@ -239,7 +113,7 @@ def test_sequence_parse_anytype_obj():
             '{http://www.w3.org/2001/XMLSchema}Schema',
             targetNamespace='http://tests.python-zeep.org/'))
 
-    root = next(schema.documents)
+    root = schema.root_document
     root.register_type('{http://tests.python-zeep.org/}something', value_type)
 
     custom_type = xsd.Element(
@@ -264,147 +138,6 @@ def test_sequence_parse_anytype_obj():
     assert obj.item_1.value == 100
 
 
-def test_sequence_parse_choice():
-    schema_doc = load_xml(b"""
-        <?xml version="1.0" encoding="utf-8"?>
-        <schema
-            xmlns="http://www.w3.org/2001/XMLSchema"
-            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
-            xmlns:tns="http://tests.python-zeep.org/tst"
-            elementFormDefault="qualified"
-            targetNamespace="http://tests.python-zeep.org/tst">
-          <element name="container">
-            <complexType>
-              <sequence>
-                <choice>
-                  <element name="item_1" type="xsd:string" />
-                  <element name="item_2" type="xsd:string" />
-                </choice>
-                <element name="item_3" type="xsd:string" />
-              </sequence>
-            </complexType>
-          </element>
-        </schema>
-    """)
-
-    xml = load_xml(b"""
-        <?xml version="1.0" encoding="utf-8"?>
-        <tst:container
-            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
-            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-            xmlns:tst="http://tests.python-zeep.org/tst">
-          <tst:item_1>blabla</tst:item_1>
-          <tst:item_3>haha</tst:item_3>
-        </tst:container>
-    """)
-
-    schema = xsd.Schema(schema_doc)
-    elm = schema.get_element('{http://tests.python-zeep.org/tst}container')
-    result = elm.parse(xml, schema)
-    assert result.item_1 == 'blabla'
-    assert result.item_3 == 'haha'
-
-
-def test_sequence_parse_choice_max_occurs():
-    schema_doc = load_xml(b"""
-        <?xml version="1.0" encoding="utf-8"?>
-        <schema
-            xmlns="http://www.w3.org/2001/XMLSchema"
-            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
-            xmlns:tns="http://tests.python-zeep.org/tst"
-            elementFormDefault="qualified"
-            targetNamespace="http://tests.python-zeep.org/tst">
-          <element name="container">
-            <complexType>
-              <sequence>
-                <choice maxOccurs="2">
-                  <element name="item_1" type="xsd:string" />
-                  <element name="item_2" type="xsd:string" />
-                </choice>
-                <element name="item_3" type="xsd:string" />
-              </sequence>
-              <attribute name="item_1" type="xsd:string" use="optional" />
-              <attribute name="item_2" type="xsd:string" use="optional" />
-            </complexType>
-          </element>
-        </schema>
-    """)
-
-    xml = load_xml(b"""
-        <?xml version="1.0" encoding="utf-8"?>
-        <tst:container
-            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
-            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-            xmlns:tst="http://tests.python-zeep.org/tst">
-          <tst:item_1>item-1-1</tst:item_1>
-          <tst:item_1>item-1-2</tst:item_1>
-          <tst:item_3>item-3</tst:item_3>
-        </tst:container>
-    """)
-
-    schema = xsd.Schema(schema_doc)
-    elm = schema.get_element('{http://tests.python-zeep.org/tst}container')
-    result = elm.parse(xml, schema)
-    assert result._value_1 == [
-        {'item_1': 'item-1-1'},
-        {'item_1': 'item-1-2'},
-    ]
-
-    assert result.item_3 == 'item-3'
-
-
-def test_sequence_parse_choice_sequence_max_occurs():
-    schema_doc = load_xml(b"""
-        <?xml version="1.0" encoding="utf-8"?>
-        <schema
-            xmlns="http://www.w3.org/2001/XMLSchema"
-            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
-            xmlns:tns="http://tests.python-zeep.org/tst"
-            elementFormDefault="qualified"
-            targetNamespace="http://tests.python-zeep.org/tst">
-          <element name="container">
-            <complexType>
-              <sequence>
-                <choice maxOccurs="3">
-                  <sequence>
-                    <element name="item_1" type="xsd:string" />
-                    <element name="item_2" type="xsd:string" />
-                  </sequence>
-                  <element name="item_3" type="xsd:string" />
-                </choice>
-                <element name="item_4" type="xsd:string" />
-              </sequence>
-            </complexType>
-          </element>
-        </schema>
-    """)
-
-    xml = load_xml(b"""
-        <?xml version="1.0" encoding="utf-8"?>
-        <tst:container
-            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
-            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-            xmlns:tst="http://tests.python-zeep.org/tst">
-          <tst:item_1>text-1</tst:item_1>
-          <tst:item_2>text-2</tst:item_2>
-          <tst:item_1>text-1</tst:item_1>
-          <tst:item_2>text-2</tst:item_2>
-          <tst:item_3>text-3</tst:item_3>
-          <tst:item_4>text-4</tst:item_4>
-        </tst:container>
-    """)
-
-    schema = xsd.Schema(schema_doc)
-    elm = schema.get_element('{http://tests.python-zeep.org/tst}container')
-    result = elm.parse(xml, schema)
-    assert result._value_1 == [
-        {'item_1': 'text-1', 'item_2': 'text-2'},
-        {'item_1': 'text-1', 'item_2': 'text-2'},
-        {'item_3': 'text-3'},
-    ]
-    assert result.item_4 == 'text-4'
-
-
 def test_sequence_parse_anytype_regression_17():
     schema_doc = load_xml(b"""
         <?xml version="1.0" encoding="utf-8"?>
@@ -465,177 +198,6 @@ def test_sequence_parse_anytype_regression_17():
     assert result.getCustomFieldReturn.value.content == 'Test Solution'
 
 
-def test_sequence_min_occurs_2():
-    custom_type = xsd.Element(
-        etree.QName('http://tests.python-zeep.org/', 'authentication'),
-        xsd.ComplexType(
-            xsd.Sequence([
-                xsd.Element(
-                    etree.QName('http://tests.python-zeep.org/', 'item_1'),
-                    xsd.String()),
-                xsd.Element(
-                    etree.QName('http://tests.python-zeep.org/', 'item_2'),
-                    xsd.String()),
-            ], min_occurs=2, max_occurs=2)
-        ))
-
-    # INIT
-    elm = custom_type(_value_1=[
-        {'item_1': 'foo-1', 'item_2': 'bar-1'},
-        {'item_1': 'foo-2', 'item_2': 'bar-2'},
-    ])
-
-    assert elm._value_1 == [
-        {'item_1': 'foo-1', 'item_2': 'bar-1'},
-        {'item_1': 'foo-2', 'item_2': 'bar-2'},
-    ]
-
-    expected = etree.fromstring("""
-        <ns0:container xmlns:ns0="http://tests.python-zeep.org/">
-          <ns0:item_1>foo</ns0:item_1>
-          <ns0:item_2>bar</ns0:item_2>
-          <ns0:item_1>foo</ns0:item_1>
-          <ns0:item_2>bar</ns0:item_2>
-        </ns0:container>
-    """)
-    obj = custom_type.parse(expected, None)
-    assert obj._value_1 == [
-        {
-            'item_1': 'foo',
-            'item_2': 'bar',
-        },
-        {
-            'item_1': 'foo',
-            'item_2': 'bar',
-        },
-    ]
-
-
-def test_all_basic():
-    custom_type = xsd.Element(
-        etree.QName('http://tests.python-zeep.org/', 'authentication'),
-        xsd.ComplexType(
-            xsd.All([
-                xsd.Element(
-                    etree.QName('http://tests.python-zeep.org/', 'item_1'),
-                    xsd.String()),
-                xsd.Element(
-                    etree.QName('http://tests.python-zeep.org/', 'item_2'),
-                    xsd.String()),
-            ])
-        ))
-    expected = etree.fromstring("""
-        <ns0:container xmlns:ns0="http://tests.python-zeep.org/">
-          <ns0:item_2>bar</ns0:item_2>
-          <ns0:item_1>foo</ns0:item_1>
-        </ns0:container>
-    """)
-    obj = custom_type.parse(expected, None)
-    assert obj.item_1 == 'foo'
-    assert obj.item_2 == 'bar'
-
-
-def test_group_optional():
-    custom_type = xsd.Element(
-        etree.QName('http://tests.python-zeep.org/', 'authentication'),
-        xsd.ComplexType(
-            xsd.Group(
-                etree.QName('http://tests.python-zeep.org/', 'foobar'),
-                xsd.Sequence([
-                    xsd.Element(
-                        etree.QName('http://tests.python-zeep.org/', 'item_1'),
-                        xsd.String()),
-                    xsd.Element(
-                        etree.QName('http://tests.python-zeep.org/', 'item_2'),
-                        xsd.String()),
-                ]),
-                min_occurs=1)
-        ))
-    expected = etree.fromstring("""
-        <ns0:container xmlns:ns0="http://tests.python-zeep.org/">
-          <ns0:item_1>foo</ns0:item_1>
-          <ns0:item_2>bar</ns0:item_2>
-        </ns0:container>
-    """)
-    obj = custom_type.parse(expected, None)
-    assert obj.item_1 == 'foo'
-    assert obj.item_2 == 'bar'
-    assert not hasattr(obj, 'foobar')
-
-
-def test_group_min_occurs_2():
-    custom_type = xsd.Element(
-        etree.QName('http://tests.python-zeep.org/', 'authentication'),
-        xsd.ComplexType(
-            xsd.Group(
-                etree.QName('http://tests.python-zeep.org/', 'foobar'),
-                xsd.Sequence([
-                    xsd.Element(
-                        etree.QName('http://tests.python-zeep.org/', 'item_1'),
-                        xsd.String()),
-                    xsd.Element(
-                        etree.QName('http://tests.python-zeep.org/', 'item_2'),
-                        xsd.String()),
-                ]),
-                min_occurs=2, max_occurs=2)
-        ))
-    expected = etree.fromstring("""
-        <ns0:container xmlns:ns0="http://tests.python-zeep.org/">
-          <ns0:item_1>foo</ns0:item_1>
-          <ns0:item_2>bar</ns0:item_2>
-          <ns0:item_1>foo</ns0:item_1>
-          <ns0:item_2>bar</ns0:item_2>
-        </ns0:container>
-    """)
-    obj = custom_type.parse(expected, None)
-    assert obj._value_1 == [
-        {'item_1': 'foo', 'item_2': 'bar'},
-        {'item_1': 'foo', 'item_2': 'bar'},
-    ]
-    assert not hasattr(obj, 'foobar')
-
-
-def test_group_min_occurs_2_sequence_min_occurs_2():
-    custom_type = xsd.Element(
-        etree.QName('http://tests.python-zeep.org/', 'authentication'),
-        xsd.ComplexType(
-            xsd.Group(
-                etree.QName('http://tests.python-zeep.org/', 'foobar'),
-                xsd.Sequence([
-                    xsd.Element(
-                        etree.QName('http://tests.python-zeep.org/', 'item_1'),
-                        xsd.String()),
-                    xsd.Element(
-                        etree.QName('http://tests.python-zeep.org/', 'item_2'),
-                        xsd.String()),
-                ], min_occurs=2, max_occurs=2),
-                min_occurs=2, max_occurs=2)
-        ))
-    expected = etree.fromstring("""
-        <ns0:container xmlns:ns0="http://tests.python-zeep.org/">
-          <ns0:item_1>foo</ns0:item_1>
-          <ns0:item_2>bar</ns0:item_2>
-          <ns0:item_1>foo</ns0:item_1>
-          <ns0:item_2>bar</ns0:item_2>
-          <ns0:item_1>foo</ns0:item_1>
-          <ns0:item_2>bar</ns0:item_2>
-          <ns0:item_1>foo</ns0:item_1>
-          <ns0:item_2>bar</ns0:item_2>
-        </ns0:container>
-    """)
-    obj = custom_type.parse(expected, None)
-    assert obj._value_1 == [
-        {'_value_1': [
-            {'item_1': 'foo', 'item_2': 'bar'},
-            {'item_1': 'foo', 'item_2': 'bar'},
-        ]},
-        {'_value_1': [
-            {'item_1': 'foo', 'item_2': 'bar'},
-            {'item_1': 'foo', 'item_2': 'bar'},
-        ]},
-    ]
-    assert not hasattr(obj, 'foobar')
-
 
 def test_nested_complex_type():
     custom_type = xsd.Element(
@@ -723,7 +285,7 @@ def test_nested_complex_type_optional():
     """)
     obj = custom_type.parse(expected, None)
     assert obj.item_1 == 'foo'
-    assert obj.item_2 == []
+    assert obj.item_2 == [None]
 
     expected = etree.fromstring("""
         <ns0:container xmlns:ns0="http://tests.python-zeep.org/">
@@ -885,3 +447,19 @@ def test_parse_invalid_values():
     assert result.item_2 == datetime.date(2016, 10, 20)
     assert result.attr_1 is None
     assert result.attr_2 == datetime.date(2013, 10, 20)
+
+
+def test_xsd_missing_localname():
+    schema = xsd.Schema(load_xml(b"""
+        <?xml version="1.0" encoding="utf-8"?>
+        <schema
+            xmlns="http://www.w3.org/2001/XMLSchema"
+            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+            xmlns:tns="http://tests.python-zeep.org/"
+            elementFormDefault="qualified"
+            targetNamespace="http://tests.python-zeep.org/">
+          <element name="container" type="xsd:"/>
+        </schema>
+    """))
+
+    schema.get_element('{http://tests.python-zeep.org/}container')
diff --git a/tests/test_xsd_schemas.py b/tests/test_xsd_schemas.py
index a629e46..44a7a96 100644
--- a/tests/test_xsd_schemas.py
+++ b/tests/test_xsd_schemas.py
@@ -4,6 +4,8 @@ from lxml import etree
 from tests.utils import DummyTransport, load_xml
 from zeep import exceptions, xsd
 from zeep.xsd import Schema
+from zeep.xsd.types.unresolved import UnresolvedType
+from tests.utils import assert_nodes_equal, load_xml, render_node
 
 
 def test_default_types():
@@ -98,7 +100,7 @@ def test_invalid_localname_handling():
 
 def test_schema_repr_none():
     schema = xsd.Schema()
-    assert repr(schema) == "<Schema(location='<none>')>"
+    assert repr(schema) == "<Schema()>"
 
 
 def test_schema_repr_val():
@@ -111,7 +113,7 @@ def test_schema_repr_val():
             elementFormDefault="qualified">
         </xs:schema>
     """))
-    assert repr(schema) == "<Schema(location=None)>"
+    assert repr(schema) == "<Schema(location=None, tns='http://tests.python-zeep.org/')>"
 
 
 def test_schema_doc_repr_val():
@@ -433,8 +435,8 @@ def test_duplicate_target_namespace():
 
     elm_b = schema.get_element('{http://tests.python-zeep.org/duplicate}elm-in-b')
     elm_c = schema.get_element('{http://tests.python-zeep.org/duplicate}elm-in-c')
-    assert not isinstance(elm_b.type, xsd.UnresolvedType)
-    assert not isinstance(elm_c.type, xsd.UnresolvedType)
+    assert not isinstance(elm_b.type, UnresolvedType)
+    assert not isinstance(elm_c.type, UnresolvedType)
 
 
 def test_multiple_no_namespace():
@@ -644,6 +646,136 @@ def test_include_recursion():
     schema.get_element('{http://tests.python-zeep.org/b}bar')
 
 
+def test_include_relative():
+    node_a = etree.fromstring("""
+        <?xml version="1.0"?>
+        <xs:schema
+            xmlns="http://tests.python-zeep.org/tns"
+            xmlns:xs="http://www.w3.org/2001/XMLSchema"
+            targetNamespace="http://tests.python-zeep.org/a"
+            elementFormDefault="qualified">
+
+            <xs:include schemaLocation="http://tests.python-zeep.org/subdir/b.xsd"/>
+
+        </xs:schema>
+    """.strip())
+
+    node_b = etree.fromstring("""
+        <?xml version="1.0"?>
+        <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
+            <xs:include schemaLocation="c.xsd"/>
+            <xs:element name="bar" type="xs:string"/>
+        </xs:schema>
+    """.strip())
+
+    node_c = etree.fromstring("""
+        <?xml version="1.0"?>
+        <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
+            <xs:element name="foo" type="xs:string"/>
+        </xs:schema>
+    """.strip())
+
+    transport = DummyTransport()
+    transport.bind('http://tests.python-zeep.org/subdir/b.xsd', node_b)
+    transport.bind('http://tests.python-zeep.org/subdir/c.xsd', node_c)
+
+    schema = xsd.Schema(node_a, transport=transport)
+    schema.get_element('{http://tests.python-zeep.org/a}foo')
+    schema.get_element('{http://tests.python-zeep.org/a}bar')
+
+
+def test_include_no_default_namespace():
+    node_a = etree.fromstring("""
+        <?xml version="1.0"?>
+        <xs:schema
+            xmlns="http://tests.python-zeep.org/tns"
+            xmlns:xs="http://www.w3.org/2001/XMLSchema"
+            targetNamespace="http://tests.python-zeep.org/tns"
+            elementFormDefault="qualified">
+
+            <xs:include
+                schemaLocation="http://tests.python-zeep.org/b.xsd"/>
+
+            <xs:element name="container" type="foo"/>
+        </xs:schema>
+    """.strip())
+
+    # include without default namespace, other xsd prefix
+    node_b = etree.fromstring("""
+        <?xml version="1.0"?>
+        <xsd:schema
+            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+            xmlns:tns="http://tests.python-zeep.org/b">
+
+            <xsd:simpleType name="my-string">
+              <xsd:restriction base="xsd:boolean"/>
+            </xsd:simpleType>
+
+            <xsd:complexType name="foo">
+              <xsd:sequence>
+                <xsd:element name="item" type="my-string"/>
+              </xsd:sequence>
+            </xsd:complexType>
+        </xsd:schema>
+    """.strip())
+
+    transport = DummyTransport()
+    transport.bind('http://tests.python-zeep.org/b.xsd', node_b)
+    schema = xsd.Schema(node_a, transport=transport)
+    item = schema.get_element('{http://tests.python-zeep.org/tns}container')
+    assert item
+
+
+def test_include_different_form_defaults():
+    node_a = etree.fromstring("""
+        <?xml version="1.0"?>
+        <xs:schema
+            xmlns="http://tests.python-zeep.org/"
+            xmlns:xs="http://www.w3.org/2001/XMLSchema"
+            targetNamespace="http://tests.python-zeep.org/">
+
+            <xs:include
+                schemaLocation="http://tests.python-zeep.org/b.xsd"/>
+        </xs:schema>
+    """.strip())
+
+    # include without default namespace, other xsd prefix
+    node_b = load_xml("""
+        <?xml version="1.0"?>
+        <xsd:schema
+            elementFormDefault="qualified"
+            attributeFormDefault="qualified"
+            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+            xmlns:tns="http://tests.python-zeep.org/b">
+
+            <xsd:element name="container" type="foo"/>
+
+            <xsd:complexType name="foo">
+              <xsd:sequence>
+                <xsd:element name="item" type="xsd:string"/>
+              </xsd:sequence>
+              <xsd:attribute name="attr" type="xsd:string"/>
+            </xsd:complexType>
+        </xsd:schema>
+    """)
+
+    transport = DummyTransport()
+    transport.bind('http://tests.python-zeep.org/b.xsd', node_b)
+
+    schema = xsd.Schema(node_a, transport=transport)
+    item = schema.get_element('{http://tests.python-zeep.org/}container')
+    obj = item(item='foo', attr='bar')
+    node = render_node(item, obj)
+
+    expected = load_xml("""
+        <document>
+          <ns0:container xmlns:ns0="http://tests.python-zeep.org/" ns0:attr="bar">
+            <ns0:item>foo</ns0:item>
+          </ns0:container>
+        </document>
+    """)
+    assert_nodes_equal(expected, node)
+
 def test_merge():
     node_a = etree.fromstring("""
         <?xml version="1.0"?>
@@ -674,3 +806,37 @@ def test_merge():
 
     schema_a.get_element('{http://tests.python-zeep.org/a}foo')
     schema_a.get_element('{http://tests.python-zeep.org/b}foo')
+
+
+def test_xml_namespace():
+    xmlns = load_xml("""
+        <?xml version="1.0"?>
+        <xs:schema
+            xmlns:xs="http://www.w3.org/2001/XMLSchema"
+            xmlns:xml="http://www.w3.org/XML/1998/namespace"
+            targetNamespace="http://www.w3.org/XML/1998/namespace"
+            elementFormDefault="qualified">
+          <xs:attribute name="lang" type="xs:string"/>
+        </xs:schema>
+    """)
+
+    transport = DummyTransport()
+    transport.bind('http://www.w3.org/2001/xml.xsd', xmlns)
+
+    xsd.Schema(load_xml("""
+        <?xml version="1.0"?>
+        <xs:schema
+            xmlns:xs="http://www.w3.org/2001/XMLSchema"
+            xmlns:tns="http://tests.python-zeep.org/"
+            targetNamespace="http://tests.python-zeep.org/"
+            elementFormDefault="qualified">
+          <xs:import namespace="http://www.w3.org/XML/1998/namespace"
+                     schemaLocation="http://www.w3.org/2001/xml.xsd"/>
+          <xs:element name="container">
+            <xs:complexType>
+              <xs:sequence/>
+              <xs:attribute ref="xml:lang"/>
+            </xs:complexType>
+          </xs:element>
+        </xs:schema>
+    """), transport=transport)
diff --git a/tests/test_xsd_signatures.py b/tests/test_xsd_signatures.py
index 0040d6a..1bcf48e 100644
--- a/tests/test_xsd_signatures.py
+++ b/tests/test_xsd_signatures.py
@@ -1,6 +1,7 @@
 from lxml import etree
 
 from zeep import xsd
+from tests.utils import load_xml
 
 
 def test_signature_complex_type_choice():
@@ -16,7 +17,7 @@ def test_signature_complex_type_choice():
                     xsd.String()),
             ])
         ))
-    assert custom_type.signature() == '({item_1: xsd:string} | {item_2: xsd:string})'
+    assert custom_type.signature() == '{http://tests.python-zeep.org/}authentication(({item_1: xsd:string} | {item_2: xsd:string}))'
 
 
 def test_signature_complex_type_choice_sequence():
@@ -38,7 +39,7 @@ def test_signature_complex_type_choice_sequence():
             ])
         ))
     assert custom_type.signature() == (
-        '({item_1: xsd:string} | {item_2_1: xsd:string, item_2_2: xsd:string})')
+        '{http://tests.python-zeep.org/}authentication(({item_1: xsd:string} | {item_2_1: xsd:string, item_2_2: xsd:string}))')
 
 
 def test_signature_nested_sequences():
@@ -80,7 +81,7 @@ def test_signature_nested_sequences():
         ))
 
     assert custom_type.signature() == (
-        'item_1: xsd:string, item_2: xsd:string, item_3: xsd:string, item_4: xsd:string, ({item_5: xsd:string} | {item_6: xsd:string} | {item_5: xsd:string, item_6: xsd:string})'  # noqa
+        '{http://tests.python-zeep.org/}authentication(item_1: xsd:string, item_2: xsd:string, item_3: xsd:string, item_4: xsd:string, ({item_5: xsd:string} | {item_6: xsd:string} | {item_5: xsd:string, item_6: xsd:string}))'
     )
 
 
@@ -123,7 +124,7 @@ def test_signature_nested_sequences_multiple():
         ))
 
     assert custom_type.signature() == (
-        'item_1: xsd:string, item_2: xsd:string, item_3: xsd:string, item_4: xsd:string, _value_1: ({item_5: xsd:string} | {item_6: xsd:string} | {item_5: xsd:string, item_6: xsd:string})[]'  # noqa
+        '{http://tests.python-zeep.org/}authentication(item_1: xsd:string, item_2: xsd:string, item_3: xsd:string, item_4: xsd:string, ({item_5: xsd:string} | {item_6: xsd:string} | {item_5: xsd:string, item_6: xsd:string})[])'
     )
 
 
@@ -138,7 +139,7 @@ def test_signature_complex_type_any():
                 xsd.Any()
             ])
         ))
-    assert custom_type.signature() == '({item_1: xsd:string} | {_value_1: ANY})'
+    assert custom_type.signature() == '{http://tests.python-zeep.org/}authentication(({item_1: xsd:string} | {_value_1: ANY}))'
     custom_type(item_1='foo')
 
 
@@ -161,7 +162,7 @@ def test_signature_complex_type_sequence_with_any():
             ])
         ))
     assert custom_type.signature() == (
-        '({item_1: xsd:string} | {item_2: {_value_1: ANY}})')
+        '{http://tests.python-zeep.org/}authentication(({item_1: xsd:string} | {item_2: {_value_1: ANY}}))')
 
 
 def test_signature_complex_type_sequence_with_anys():
@@ -184,4 +185,30 @@ def test_signature_complex_type_sequence_with_anys():
             ])
         ))
     assert custom_type.signature() == (
-        '({item_1: xsd:string} | {item_2: {_value_1: ANY, _value_2: ANY}})')
+        '{http://tests.python-zeep.org/}authentication(' +
+        '({item_1: xsd:string} | {item_2: {_value_1: ANY, _value_2: ANY}})' +
+        ')')
+
+def test_schema_recursive_ref():
+    schema = xsd.Schema(load_xml("""
+        <?xml version="1.0"?>
+        <xsd:schema
+            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+            xmlns:tns="http://tests.python-zeep.org/"
+            targetNamespace="http://tests.python-zeep.org/"
+            elementFormDefault="qualified">
+
+          <xsd:element name="Container">
+              <xsd:complexType>
+                  <xsd:sequence>
+                      <xsd:element ref="tns:Container" />
+                  </xsd:sequence>
+              </xsd:complexType>
+          </xsd:element>
+
+        </xsd:schema>
+    """))
+
+    elm = schema.get_element('ns0:Container')
+    elm.signature(schema)
+
diff --git a/tests/test_xsd_union.py b/tests/test_xsd_union.py
index 1993ff5..679ba14 100644
--- a/tests/test_xsd_union.py
+++ b/tests/test_xsd_union.py
@@ -6,6 +6,7 @@ def test_union_same_types():
     schema = xsd.Schema(load_xml("""
         <?xml version="1.0"?>
         <xsd:schema
+            xmlns="http://tests.python-zeep.org/"
             xmlns:xsd="http://www.w3.org/2001/XMLSchema"
             xmlns:tns="http://tests.python-zeep.org/"
             targetNamespace="http://tests.python-zeep.org/"
@@ -21,7 +22,7 @@ def test_union_same_types():
           </xsd:simpleType>
 
           <xsd:simpleType name="Date">
-            <xsd:union memberTypes="MMYY MMYYYY"/>
+            <xsd:union memberTypes="tns:MMYY MMYYYY"/>
           </xsd:simpleType>
           <xsd:element name="item" type="tns:Date"/>
         </xsd:schema>
@@ -49,7 +50,7 @@ def test_union_mixed():
             elementFormDefault="qualified">
           <xsd:element name="item" type="tns:Date"/>
           <xsd:simpleType name="Date">
-            <xsd:union memberTypes="xsd:date xsd:gYear xsd:gYearMonth MMYY MMYYYY"/>
+            <xsd:union memberTypes="xsd:date xsd:gYear xsd:gYearMonth tns:MMYY tns:MMYYYY"/>
           </xsd:simpleType>
           <xsd:simpleType name="MMYY">
             <xsd:restriction base="xsd:string">
diff --git a/tests/test_xsd_valueobjects.py b/tests/test_xsd_valueobjects.py
index f9c6eaa..5874f0f 100644
--- a/tests/test_xsd_valueobjects.py
+++ b/tests/test_xsd_valueobjects.py
@@ -1,5 +1,9 @@
+import json
+import pickle
+
 import pytest
 import six
+from lxml.etree import QName
 
 from zeep import xsd
 from zeep.xsd import valueobjects
@@ -214,9 +218,10 @@ def test_choice_mixed():
                 xsd.Element('item_2', xsd.String()),
             ]),
             xsd.Element('item_2', xsd.String())
-        ])
+        ]),
+        qname=QName('http://tests.python-zeep.org', 'container')
     )
-    expected = '({item_1: xsd:string} | {item_2: xsd:string}), item_2__1: xsd:string'
+    expected = '{http://tests.python-zeep.org}container(({item_1: xsd:string} | {item_2: xsd:string}), item_2__1: xsd:string)'
     assert xsd_type.signature() == expected
 
     args = tuple([])
@@ -401,3 +406,35 @@ def test_choice_sequences_init_dict():
             {'item_1': 'value-1', 'item_2': 'value-2'}
         ]
     }
+
+
+def test_pickle():
+    xsd_type = xsd.ComplexType(
+        xsd.Sequence([
+            xsd.Element('item_1', xsd.String()),
+            xsd.Element('item_2', xsd.String())
+        ]))
+
+    obj = xsd_type(item_1='x', item_2='y')
+
+    data = pickle.dumps(obj)
+    obj_rt = pickle.loads(data)
+
+    assert obj.item_1 == 'x'
+    assert obj.item_2 == 'y'
+    assert obj_rt.item_1 == 'x'
+    assert obj_rt.item_2 == 'y'
+
+
+def test_json():
+    xsd_type = xsd.ComplexType(
+        xsd.Sequence([
+            xsd.Element('item_1', xsd.String()),
+            xsd.Element('item_2', xsd.String())
+        ]))
+
+    obj = xsd_type(item_1='x', item_2='y')
+    assert obj.__json__() == {
+        'item_1': 'x',
+        'item_2': 'y',
+    }
diff --git a/tests/test_xsd_visitor.py b/tests/test_xsd_visitor.py
index 66d561b..d5e343e 100644
--- a/tests/test_xsd_visitor.py
+++ b/tests/test_xsd_visitor.py
@@ -23,7 +23,7 @@ def test_schema_empty():
         </schema>
     """)
     schema = parse_schema_node(node)
-    root = next(schema.documents)
+    root = schema._get_schema_documents('http://tests.python-zeep.org/')[0]
     assert root._element_form == 'qualified'
     assert root._attribute_form == 'unqualified'
 
diff --git a/tests/wsdl_files/soap.wsdl b/tests/wsdl_files/soap.wsdl
index ab28e67..5783270 100644
--- a/tests/wsdl_files/soap.wsdl
+++ b/tests/wsdl_files/soap.wsdl
@@ -1,13 +1,13 @@
 <?xml version="1.0"?>
-<definitions 
-    xmlns:tns="http://example.com/stockquote.wsdl" 
-    xmlns:xsd1="http://example.com/stockquote.xsd" 
-    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" 
+<definitions
+    xmlns:tns="http://example.com/stockquote.wsdl"
+    xmlns:xsd1="http://example.com/stockquote.xsd"
+    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
     xmlns="http://schemas.xmlsoap.org/wsdl/"
-    name="StockQuote" 
+    name="StockQuote"
     targetNamespace="http://example.com/stockquote.wsdl">
   <types>
-    <schema xmlns="http://www.w3.org/2001/XMLSchema" 
+    <schema xmlns="http://www.w3.org/2001/XMLSchema"
             targetNamespace="http://example.com/stockquote.xsd"
             xmlns:tns="http://example.com/stockquote.xsd" >
       <complexType name="Address">
@@ -17,6 +17,12 @@
           <element minOccurs="0" maxOccurs="1" name="Email" type="string"/>
         </sequence>
       </complexType>
+      <complexType name="ArrayOfAddress">
+          <sequence>
+              <element name="Address" type="tns:Address" minOccurs="0" maxOccurs="unbounded"/>
+          </sequence>
+      </complexType>
+
       <element name="Fault1">
         <complexType>
           <sequence>
@@ -87,6 +93,9 @@
       <fault message="tns:FaultMessageMsg1" name="fault1"/>
       <fault message="tns:FaultMessageMsg2" name="fault2"/>
     </operation>
+    <operation name="GetLastTradePriceNoOutput">
+      <input message="tns:GetLastTradePriceInput"/>
+    </operation>
   </portType>
   <binding name="StockQuoteBinding" type="tns:StockQuotePortType">
     <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
@@ -105,6 +114,12 @@
         <soap:fault name="fault2" use="literal"/>
       </fault>
     </operation>
+    <operation name="GetLastTradePriceNoOutput">
+      <soap:operation soapAction="http://example.com/GetLastTradePrice"/>
+      <input>
+        <soap:body use="literal"/>
+      </input>
+    </operation>
   </binding>
   <service name="StockQuoteService">
     <documentation>My first service</documentation>
diff --git a/tests/wsdl_files/soap_header.wsdl b/tests/wsdl_files/soap_header.wsdl
index 04e1269..aa1e169 100644
--- a/tests/wsdl_files/soap_header.wsdl
+++ b/tests/wsdl_files/soap_header.wsdl
@@ -1,7 +1,16 @@
 <?xml version="1.0"?>
-<definitions xmlns:tns="http://example.com/stockquote.wsdl" xmlns:xsd1="http://example.com/stockquote.xsd" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns="http://schemas.xmlsoap.org/wsdl/" name="StockQuote" targetNamespace="http://example.com/stockquote.wsdl">
+<definitions 
+  xmlns:tns="http://example.com/stockquote.wsdl" 
+  xmlns:xsd1="http://example.com/stockquote.xsd" 
+  xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" 
+  xmlns="http://schemas.xmlsoap.org/wsdl/" 
+  xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
+  name="StockQuote" 
+  targetNamespace="http://example.com/stockquote.wsdl">
   <types>
-    <schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://example.com/stockquote.xsd">
+    <schema 
+      xmlns="http://www.w3.org/2001/XMLSchema" 
+      targetNamespace="http://example.com/stockquote.xsd">
       <complexType name="Address">
         <sequence>
           <element minOccurs="0" maxOccurs="1" name="NameFirst" type="string"/>
@@ -48,6 +57,10 @@
   <message name="GetLastTradePriceOutput">
     <part name="body" element="xsd1:TradePrice"/>
   </message>
+  <message name="OptionalResponseHeader">
+    <part name="body" type="xsd:string"/>
+  </message>
+
   <portType name="StockQuotePortType">
     <operation name="GetLastTradePrice">
       <input message="tns:GetLastTradePriceInput"/>
@@ -65,6 +78,7 @@
       </input>
       <output>
         <soap:body use="literal"/>
+        <soap:header message="tns:OptionalResponseHeader" part="body" use="literal"/>
       </output>
     </operation>
   </binding>
-- 
python-zeep



More information about the tryton-debian-vcs mailing list