summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore57
-rw-r--r--README.txt14
-rw-r--r--doc/conf.py2
-rw-r--r--doc/pyxbgen_cli.txt9
-rw-r--r--doc/releases.txt23
-rw-r--r--examples/geocoder/client.py2
-rw-r--r--maintainer/howto-release.txt63
-rwxr-xr-xmaintainer/makedist29
-rwxr-xr-xmaintainer/testdist.sh2
-rw-r--r--pyxb/__init__.py2
-rw-r--r--pyxb/binding/basis.py48
-rw-r--r--pyxb/binding/content.py1253
-rw-r--r--pyxb/binding/datatypes.py12
-rw-r--r--pyxb/binding/generate.py239
-rw-r--r--pyxb/binding/nfa.py392
-rwxr-xr-xpyxb/bundles/common/scripts/genbind4
-rwxr-xr-xpyxb/bundles/opengis/scripts/genbind6
-rw-r--r--pyxb/exceptions_.py8
-rw-r--r--pyxb/utils/utility.py35
-rw-r--r--pyxb/xmlschema/structures.py8
-rwxr-xr-xsetup.py2
-rw-r--r--tests/bindings/test-constraints.py7
-rw-r--r--tests/bindings/test-nfa.py199
-rw-r--r--tests/drivers/test-ctd-extension.py6
-rw-r--r--tests/trac/test-trac-0033a.py54
-rwxr-xr-xtests/trac/trac-0033/test.sh7
-rw-r--r--tests/trac/trac-0033/tread.py51
-rw-r--r--tests/trac/trac-0080/check.py65
-rw-r--r--tests/trac/trac-0080/multipleRestriction.xsd53
-rwxr-xr-xtests/trac/trac-0080/test.sh2
-rw-r--r--tests/trac/trac-0084/example.xsd15
-rwxr-xr-xtests/trac/trac-0084/test.sh10
-rw-r--r--tests/trac/trac-0084/tryit.py1
33 files changed, 1283 insertions, 1397 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..54ac488
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,57 @@
+*~
+*.pyc
+*.pyo
+*.wxs
+doc/html
+doc/_build
+raw/
+pyxb/bundles/[^/]*/schemas
+pyxb/bundles/[^/]*/remote
+.*/raw
+doc/html
+doc/_build
+W3C
+code.py
+build
+examples/content/content.py
+examples/content/test.log
+examples/dictionary/dict.py
+examples/dictionary/dict.wsdl
+examples/dictionary/test.log
+examples/geocoder/GeoCoder.py
+examples/geocoder/GeoCoder.wsdl
+examples/geocoder/test.log
+examples/ndfd/DWML.py
+examples/ndfd/NDFDgen.xml
+examples/ndfd/forecast.xml
+examples/ndfd/ndfdXML.wsdl
+examples/ndfd/test.log
+examples/ndfd/uriArchive/DWML.xsd
+examples/ndfd/uriArchive/location.xsd
+examples/ndfd/uriArchive/meta_data.xsd
+examples/ndfd/uriArchive/moreWeatherInformation.xsd
+examples/ndfd/uriArchive/ndfd_data.xsd
+examples/ndfd/uriArchive/parameters.xsd
+examples/ndfd/uriArchive/time_layout.xsd
+examples/tmsxtvd/test.log
+examples/tmsxtvd/tmsdatadirect_sample.xml
+examples/tmsxtvd/tmstvd.py
+examples/weather/test.log
+examples/weather/weather.py
+pyxb/bundles/opengis/examples/gmlapp.py
+pyxb/bundles/opengis/examples/test.log
+tests/complex/nsdep/bindings/D.py
+tests/complex/nsdep/bindings/_A.py
+tests/complex/nsdep/bindings/_B.py
+tests/complex/nsdep/bindings/__init__.py
+tests/complex/nsdep/bindings/_nsgroup.py
+tests/complex/nsdep/test.log
+tests/complex/nsext/app.py
+tests/complex/nsext/bad.log
+tests/complex/nsext/common.py
+tests/complex/nsext/common4app.py
+tests/complex/nsext/test.log
+tests/complex/trac0043/A.py
+tests/complex/trac0043/B.py
+tests/complex/trac0043/_nsgroup.py
+tests/complex/trac0043/test.log
diff --git a/README.txt b/README.txt
index cfa5a03..1c0b667 100644
--- a/README.txt
+++ b/README.txt
@@ -1,13 +1,13 @@
PyXB -- Python W3C XML Schema Bindings
-Version 1.1.1
+Version 1.1.2
Distribution components:
- PyXB-base-1.1.1.tar.gz -- Complete release, nothing pre-built
- PyXB-doc-1.1.1.tar.gz -- Overlay with pre-built documentation
- PyXB-common-1.1.1.tar.gz -- Overlay with XHTML bindings
- PyXB-opengis-1.1.1.tar.gz -- Overlay with OpenGIS bindings
- PyXB-wsspat-1.1.1.tar.gz -- Overlay with WS-* bindings
- PyXB-full-1.1.1.tar.gz -- Complete release with all overlays
+ PyXB-base-1.1.2.tar.gz -- Complete release, nothing pre-built
+ PyXB-doc-1.1.2.tar.gz -- Overlay with pre-built documentation
+ PyXB-common-1.1.2.tar.gz -- Overlay with XHTML bindings
+ PyXB-opengis-1.1.2.tar.gz -- Overlay with OpenGIS bindings
+ PyXB-wsspat-1.1.2.tar.gz -- Overlay with WS-* bindings
+ PyXB-full-1.1.2.tar.gz -- Complete release with all overlays
Installation: python setup.py install
diff --git a/doc/conf.py b/doc/conf.py
index c557dc6..36ecd32 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -47,7 +47,7 @@ copyright = u'2009, Peter A. Bigot'
# The short X.Y version.
version = '1.1'
# The full version, including alpha/beta/rc tags.
-release = '1.1.1'
+release = '1.1.2'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
diff --git a/doc/pyxbgen_cli.txt b/doc/pyxbgen_cli.txt
index 58728f7..1ac9055 100644
--- a/doc/pyxbgen_cli.txt
+++ b/doc/pyxbgen_cli.txt
@@ -41,6 +41,7 @@ Specify and locate schema for which bindings should be generated.
``--schema-location`` *FILE_or_URL* ``-u`` :ref:`Add the location of an entrypoint schema. The...<pyxbgen--schema-location>`
``--schema-root`` *DIRECTORY* :ref:`The directory from which entrypoint schemas...<pyxbgen--schema-root>`
``--schema-stripped-prefix`` *TEXT* :ref:`Optional string that is stripped from the...<pyxbgen--schema-stripped-prefix>`
+ ``--location-prefix-rewrite`` *TEXT* :ref:`Add a rewrite entry for schema locations....<pyxbgen--location-prefix-rewrite>`
``--uri-content-archive-directory`` *DIRECTORY* :ref:`The directory path into which any content...<pyxbgen--uri-content-archive-directory>`
=================================== ============= ====== ==================================================
@@ -71,6 +72,14 @@ purpose is to convert absolute schema locations into relative ones to
allow offline processing when all schema are available in a local
directory. See ``schemaRoot``.
+.. _pyxbgen--location-prefix-rewrite:
+
+``--location-prefix-rewrite``
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Add a rewrite entry for schema locations. Parameter values are strings
+of the form ``pfx=sub``. The effect is that a schema location that
+begins with ``pfx`` is rewritten so that it instead begins with ``sub``.
+
.. _pyxbgen--uri-content-archive-directory:
``--uri-content-archive-directory``
diff --git a/doc/releases.txt b/doc/releases.txt
index 13e042c..4dc60de 100644
--- a/doc/releases.txt
+++ b/doc/releases.txt
@@ -40,6 +40,29 @@ Linux several years ago:
1.1.x (Beta)
==============
+1.1.2 (30 May 2010)
+-------------------
+
+Complete replacement of the model group portion of the content model. The
+NFA-to-DFA approach is gone. The resulting system does a better job in less
+space and significantly faster: 30% on the standard tmsxtvd test, orders of
+magnitude on documents with large sequences of optional elements.
+
+Note: A couple unit tests fail due to incidental reasons that will be address
+when fixing :ticket:`75`. Also, the architecture documentation for the
+validation portion of the content model is completely out of date.
+
+- Handle sequences of optional elements more effectively. :ticket:`33`
+
+- Correct multi-step attribute restriction/extension. :ticket:`80`
+
+- Support prefix rewrite for schema location URIs. :ticket:`81`
+
+- Fix syntax error generating wildcard namespace constraints. :ticket:`84`
+
+- Support whitespace validation bypass in simple type definitions. :ticket:`85`
+
+
1.1.1 (28 Jan 2010, rev 2135)
-----------------------------
diff --git a/examples/geocoder/client.py b/examples/geocoder/client.py
index 0b4b8ce..bbad451 100644
--- a/examples/geocoder/client.py
+++ b/examples/geocoder/client.py
@@ -10,7 +10,7 @@ address = '1600 Pennsylvania Ave., Washington, DC'
if 1 < len(sys.argv):
address = sys.argv[1]
-env = soapenv.Envelope(BIND(GeoCoder.geocode(address)))
+env = soapenv.Envelope(Body=BIND(GeoCoder.geocode(address)))
uri = urllib2.Request('http://rpc.geocoder.us/service/soap/',
env.toxml(),
diff --git a/maintainer/howto-release.txt b/maintainer/howto-release.txt
new file mode 100644
index 0000000..3ebb19e
--- /dev/null
+++ b/maintainer/howto-release.txt
@@ -0,0 +1,63 @@
+* Update the release notes in doc/releases.txt. All fixed DRs should be
+ noted, as well as any major new features.
+
+* Update the version number in setup.py. Run:
+
+ ./setup.py update_version
+ hg commit -m 'Version number updated for release'
+
+* Run the maintainer/makedist script. This exports the current directory's
+ repository into /tmp, then does a full setup, documentation build, and
+ test in that area. It creates the variant bundles, and places them in
+ ~/pyxb/pre-release.
+
+* In ~/pyxb/pre-release, run the testdist.sh script. This unpacks the full
+ distribution, builds, installs, and runs the full test sequence on three
+ versions of Python. Check out the logs for any errors.
+
+* When all tests pass, move the release tar files into ~/pyxb/pyxb-release.
+ Create a file notice-A.B.C.txt containing the section of doc/releases.txt
+ relevant to the release. Add all these to the repository, and push it.
+
+* Back in the development repository, update setup.py for the next release
+ series, run ./setup.py update_version, and commit the changes.
+
+* Log in to SourceForge, and under Project Admin enter File Manager. Create
+ a new release directory under pyxb, then upload each files. Later files
+ come earlier in the list, so a good order is: opengis, common, wssplat,
+ base, doc, full.
+
+* For each upload, click on the gear and set its properties:
+
+ notice-A.B.C.txt -- Release notes (mark as release notes)
+ full -- Full release with all overlays (mark as default download)
+ doc -- Documentation overlay
+ base -- Base release, no overlays
+ wssplt -- Web service bindings overlay
+ common -- Common schema bindings overlay
+ opengis -- OpenGIS bindings overlay
+
+ Also mark the full release as being the default download.
+
+* Associate the release notes with the folder and the full release.
+
+* Go to the Trac system. Under Milestones
+
+ - Edit the milestone for the new release, setting its Completed date
+ - Add the next beta series as a milestone, marking it as the default
+ - Add the new release as a version, marking it the default
+
+* Edit the main Trac page to select the correct versions for the DR tables.
+ An example of a full page is in maintainer/trac-source.
+
+* Log in to sourceforge and replace the homepage with the new documentation.
+
+ ssh -t pabigot,pyxb@shell.sourceforge.net create
+ cd /home/groups/p/py/pyxb/htdocs
+ mkdir PyXB-1.1.0
+ mv _* [a-z]* PyXB-1.1.0
+ tar xzf /home/frs/project/p/py/pyxb/pyxb/1.1.1\ \(Beta\)/PyXB-doc-1.1.1.tar.gz
+ mv PyXB-1.1.1/doc/html/* .
+ rm -rf PyXB-1.1.1/
+
+* Mail the release notes to pyxb-users@lists.sourceforge.net
diff --git a/maintainer/makedist b/maintainer/makedist
index 8deb774..035cfc3 100755
--- a/maintainer/makedist
+++ b/maintainer/makedist
@@ -1,23 +1,28 @@
#!/bin/sh
-exec 2>&1 | tee makedist.log
+BRANCH=${1:-next}
+
+exec | tee makedist.log 2>&1
+
+DIST_SUBDIR=pyxbdist
+cd /tmp
+rm -rf ${DIST_SUBDIR}
+git clone -b ${BRANCH} ~/pyxb/dev ${DIST_SUBDIR}
+cd ${DIST_SUBDIR}
+export PYXB_ROOT=`pwd`
# Update the version numbers in the source
-./setup.py update_version
+version=$(grep ^version setup.py | cut -d= -f2 | sed -e "s@'@@g")
-hg_status=`hg status -q | grep -v makedist`
-if [ -n "${hg_status}" ] ; then
+echo $version
+
+./setup.py update_version
+if ( git status >/dev/null ) ; then
echo 1>&2 ERROR: Unsaved deltas
- echo ${hg_status}
+ git status -uno
exit 1
fi
-DISTDIR=/tmp/pyxbdist
-rm -rf ${DISTDIR}
-hg archive ${DISTDIR}
-cd ${DISTDIR}
-export PYXB_ROOT=`pwd`
-
# First, the core:
./setup.py sdist
ver=`ls -rt dist | tail -1 | sed -e 's@^PyXB-\(.*\).tar.gz$@\1@'`
@@ -72,7 +77,7 @@ build_overlay () {
sdir=$1
shift
(
- find ${DISTROOT}/${sdir} -type f -newer ${BASE_TGZ} ;
+ find ${DISTROOT}/${sdir} -type f -cnewer ${BASE_TGZ} ;
for f in "${@}" ; do
find ${DISTROOT}/${f} -type f
done
diff --git a/maintainer/testdist.sh b/maintainer/testdist.sh
index 1042651..9af3e8b 100755
--- a/maintainer/testdist.sh
+++ b/maintainer/testdist.sh
@@ -6,7 +6,7 @@ TARFILE=PyXB-full-${RELEASE}.tar.gz
export LANG=en_US.UTF-8
-for pv in 2.4.6 2.5.4 2.6.4 ; do
+for pv in 2.4.6 2.5.5 2.6.5 ; do
(
pt=python-${pv}
pvs=`echo ${pv} | sed -e 's@..$@@'`
diff --git a/pyxb/__init__.py b/pyxb/__init__.py
index 6f44d54..06f0b95 100644
--- a/pyxb/__init__.py
+++ b/pyxb/__init__.py
@@ -53,7 +53,7 @@ class cscRoot (object):
if issubclass(self.__class__.mro()[-2], ( list, dict )):
super(cscRoot, self).__init__(*args)
-__version__ = '1.1.1'
+__version__ = '1.1.2'
"""The version of PyXB"""
__url__ = 'http://pyxb.sourceforge.net'
diff --git a/pyxb/binding/basis.py b/pyxb/binding/basis.py
index ef91f4e..63f9db4 100644
--- a/pyxb/binding/basis.py
+++ b/pyxb/binding/basis.py
@@ -396,6 +396,10 @@ class _TypeBinding_mixin (utility.Locatable_mixin):
self._validateBinding_vx()
return True
+ def _postDOMValidate (self):
+ self.validateBinding()
+ return self
+
@classmethod
def _Name (cls):
if cls._ExpandedName is not None:
@@ -541,8 +545,11 @@ class simpleTypeDefinition (_TypeBinding_mixin, utility._DeconflictSymbols_mixin
'''
nm = cls.__FacetMapAttributeNameMap.get(cls)
if nm is None:
+ nm = cls.__name__
+ if nm.endswith('_'):
+ nm += '1'
if cls == simpleTypeDefinition:
- nm = '_%s__FacetMap' % (cls.__name__.strip('_'),)
+ nm = '_%s__FacetMap' % (nm,)
else:
# It is not uncommon for a class in one namespace to extend a class of
# the same name in a different namespace, so encode the namespace URI
@@ -552,7 +559,7 @@ class simpleTypeDefinition (_TypeBinding_mixin, utility._DeconflictSymbols_mixin
ns_uri = cls._ExpandedName.namespaceURI()
except Exception, e:
pass
- nm = '_' + utility.MakeIdentifier('%s_%s_FacetMap' % (ns_uri, cls.__name__.strip('_')))
+ nm = '_' + utility.MakeIdentifier('%s_%s_FacetMap' % (ns_uri, nm))
cls.__FacetMapAttributeNameMap[cls] = nm
return nm
@@ -585,7 +592,7 @@ class simpleTypeDefinition (_TypeBinding_mixin, utility._DeconflictSymbols_mixin
except AttributeError:
pass
if fm is not None:
- raise pyxb.LogicError('%s facet map initialized multiple times: %s' % (cls.__name__,cls.__FacetMapAttributeName()))
+ raise pyxb.LogicError('%s facet map initialized multiple times: %s' % (cls.__name__, cls.__FacetMapAttributeName()))
# Search up the type hierarchy to find the nearest ancestor that has a
# facet map. This gets a bit tricky: if we hit the ceiling early
@@ -648,7 +655,7 @@ class simpleTypeDefinition (_TypeBinding_mixin, utility._DeconflictSymbols_mixin
args = (domutils.ExtractTextContent(dom_node),) + args
kw['_apply_whitespace_facet'] = True
apply_whitespace_facet = kw.pop('_apply_whitespace_facet', True)
- if (0 < len(args)) and isinstance(args[0], types.StringTypes):
+ if (0 < len(args)) and isinstance(args[0], types.StringTypes) and apply_whitespace_facet:
cf_whitespace = getattr(cls, '_CF_whiteSpace', None)
if cf_whitespace is not None:
#print 'Apply whitespace %s to "%s"' % (cf_whitespace, args[0])
@@ -1481,9 +1488,7 @@ class element (utility._DeconflictSymbols_mixin, _DynamicCreate_mixin):
rv = type_class.Factory(_dom_node=node, _fallback_namespace=fallback_namespace, **kw)
assert rv._element() == element_binding
rv._setNamespaceContext(ns_ctx)
- if pyxb._ParsingRequiresValid:
- rv.validateBinding()
- return rv
+ return rv._postDOMValidate()
def __str__ (self):
return 'Element %s' % (self.name(),)
@@ -1667,7 +1672,7 @@ class complexTypeDefinition (_TypeBinding_mixin, utility._DeconflictSymbols_mixi
_ReservedSymbols = _TypeBinding_mixin._ReservedSymbols.union(set([ 'wildcardElements', 'wildcardAttributeMap',
'xsdConstraintsOK', 'content', 'append', 'extend', 'value', 'reset' ]))
- # None, or a reference to a ContentModel instance that defines how to
+ # None, or a reference to a ParticleModel instance that defines how to
# reduce a DOM node list to the body of this element.
_ContentModel = None
@@ -1725,7 +1730,7 @@ class complexTypeDefinition (_TypeBinding_mixin, utility._DeconflictSymbols_mixi
model, C{None} is returned.
The base class implementation uses the
- L{content.ContentModel.validate} method. Subclasses may desire to
+ L{content.ParticleModel.validate} method. Subclasses may desire to
override this in cases where the desired order is not maintained by
model interpretation (for example, when an "all" model is used and the
original element order is desired). See L{__childrenForDOM} as an
@@ -1775,13 +1780,12 @@ class complexTypeDefinition (_TypeBinding_mixin, utility._DeconflictSymbols_mixi
rv[eu] = [ converter(value)]
wce = self.wildcardElements()
if (wce is not None) and (0 < len(wce)):
- rv[None] = wce
+ rv[None] = wce[:]
return rv
def _validateAttributes (self):
for au in self._AttributeMap.values():
au.validate(self)
-
def _validateBinding_vx (self):
if self._isNil():
@@ -1900,7 +1904,7 @@ class complexTypeDefinition (_TypeBinding_mixin, utility._DeconflictSymbols_mixi
else:
self.__setContent(None)
- __dfaStack = None
+ __modelState = None
def reset (self):
"""Reset the instance.
@@ -1915,7 +1919,7 @@ class complexTypeDefinition (_TypeBinding_mixin, utility._DeconflictSymbols_mixi
for eu in self._ElementMap.values():
eu.reset(self)
if self._ContentModel is not None:
- self.__dfaStack = self._ContentModel.initialDFAStack()
+ self.__modelState = self._ContentModel.newState()
return self
@classmethod
@@ -2029,7 +2033,7 @@ class complexTypeDefinition (_TypeBinding_mixin, utility._DeconflictSymbols_mixi
return self
if self._isNil() and not self._IsSimpleTypeContent():
raise pyxb.ExtraContentError('%s: Content %s present in element with xsi:nil' % (type(self), value))
- if maybe_element and (self.__dfaStack is not None):
+ if maybe_element and (self.__modelState is not None):
# Allows element content.
if not require_validation:
if element_use is not None:
@@ -2040,13 +2044,15 @@ class complexTypeDefinition (_TypeBinding_mixin, utility._DeconflictSymbols_mixi
self._appendWildcardElement(value)
return self
else:
- if self.__dfaStack.step(self, value, element_use):
+ #print 'SSStep %s %s' % (value, element_use)
+ ( consumed, underflow_exc ) = self.__modelState.step(self, value, element_use)
+ if consumed:
return self
# If what we have is element content, we can't accept it, either
# because the type doesn't accept element content or because it does
# and what we got didn't match the content model.
if (element_binding is not None) or isinstance(value, xml.dom.Node):
- raise pyxb.ExtraContentError('%s: Extra content starting with %s' % (self._ExpandedName, value,))
+ raise pyxb.ExtraContentError('%s: Extra content %s starting with %s' % (self._ExpandedName, element_binding, value,))
# We have something that doesn't seem to be an element. Are we
# expecting simple content?
@@ -2100,13 +2106,17 @@ class complexTypeDefinition (_TypeBinding_mixin, utility._DeconflictSymbols_mixi
def _IsMixed (cls):
return (cls._CT_MIXED == cls._ContentTypeTag)
+ def _postDOMValidate (self):
+ if self._PerformValidation() and (not self._isNil()) and (self.__modelState is not None):
+ self.__modelState.verifyComplete()
+ self._validateAttributes()
+ return self
+
def _setContentFromDOM (self, node, _fallback_namespace):
"""Initialize the content of this element from the content of the DOM node."""
self.extend(node.childNodes[:], _fallback_namespace)
- if self._PerformValidation() and (not self._isNil()) and (self.__dfaStack is not None) and (not self.__dfaStack.isTerminal()):
- raise pyxb.MissingContentError()
- return self
+ return self._postDOMValidate()
def _setDOMFromAttributes (self, dom_support, element):
"""Add any appropriate attributes from this instance into the DOM element."""
diff --git a/pyxb/binding/content.py b/pyxb/binding/content.py
index 4943e1d..6150739 100644
--- a/pyxb/binding/content.py
+++ b/pyxb/binding/content.py
@@ -21,16 +21,7 @@ the Python field in which the values are stored. They also provide the
low-level interface to set and get the corresponding values in a binding
instance.
-L{ContentModelTransition}, L{ContentModelState}, and L{ContentModel} are used
-to store a deterministic finite automaton which is used to translate between
-binding instances and other representations (e.g., DOM nodes)
-
-L{ModelGroupAllAlternative} and L{ModelGroupAll} represent special nodes in
-the DFA that support a model group with compositor "all" in a way that does
-not result in an exponential state explosion in the DFA.
-
-L{DFAStack} and its related internal classes are used in stream-based
-processing of content.
+@todo: Document new content model
L{Wildcard} holds content-related information used in the content model.
"""
@@ -41,6 +32,137 @@ import basis
import xml.dom
+class ContentState_mixin (pyxb.cscRoot):
+ """Declares methods used by classes that hold state while validating a
+ content model component."""
+
+ def accepts (self, particle_state, instance, value, element_use):
+ """Determine whether the provided value can be added to the instance
+ without violating state validation.
+
+ This method must not throw any non-catastrophic exceptions; general
+ failures should be transformed to a C{False} return value.
+
+ @param particle_state: The L{ParticleState} instance serving as the
+ parent to this state. The implementation must inform that state when
+ the proposed value completes the content model.
+
+ @param instance: An instance of a subclass of
+ {basis.complexTypeDefinition}, into which the provided value will be
+ stored if it is consistent with the current model state.
+
+ @param value: The value that is being validated against the state.
+
+ @param element_use: An optional L{ElementUse} instance that specifies
+ the element to which the value corresponds. This will be available
+ when the value is extracted by parsing a document, but will be absent
+ if the value was passed as a constructor positional parameter.
+
+ @return: C{True} if the value was successfully matched against the
+ state. C{False} if the value did not match against the state."""
+ raise Exception('ContentState_mixin.accepts not implemented in %s' % (type(self),))
+
+ def notifyFailure (self, sub_state, particle_ok):
+ """Invoked by a sub-state to indicate that validation cannot proceed
+ in the current state.
+
+ Normally this is used when an intermediate content model must reset
+ itself to permit alternative models to be evaluated.
+
+ @param sub_state: the state that was unable to accept a value
+
+ @param particle_ok: C{True} if the particle that rejected the value is
+ in an accepting terminal state
+
+ """
+ raise Exception('ContentState_mixin.notifyFailure not implemented in %s' % (type(self),))
+
+ def _verifyComplete (self, parent_particle_state):
+ """Determine whether the deep state is complete without further elements.
+
+ No-op for non-aggregate state. For aggregate state, all contained
+ particles should be checked to see whether the overall model can be
+ satisfied if no additional elements are provided.
+
+ This method does not have a meaningful return value; violations of the
+ content model should produce the corresponding exception (generally,
+ L{MissingContentError}).
+
+ @param parent_particle_state: the L{ParticleState} for which this state
+ is the term.
+ """
+ pass
+
+class ContentModel_mixin (pyxb.cscRoot):
+ """Declares methods used by classes representing content model components."""
+
+ def newState (self, parent_particle_state):
+ """Return a L{ContentState_mixin} instance that will validate the
+ state of this model component.
+
+ @param parent_particle_state: The L{ParticleState} instance for which
+ this instance is a term. C{None} for the top content model of a
+ complex data type.
+ """
+ raise Exception('ContentModel_mixin.newState not implemented in %s' % (type(self),))
+
+ def _validateCloneSymbolSet (self, symbol_set_im):
+ """Create a mutable copy of the symbol set.
+
+ The top-level map is copied, as are the lists of values. The values
+ within the lists are unchanged, as validation does not affect them."""
+ rv = symbol_set_im.copy()
+ for (k, v) in rv.items():
+ rv[k] = v[:]
+ return rv
+
+ def _validateCloneOutputSequence (self, output_sequence_im):
+ return output_sequence_im[:]
+
+ def _validateReplaceResults (self, symbol_set_out, symbol_set_new, output_sequence_out, output_sequence_new):
+ """In-place update of symbol set and output sequence structures.
+
+ Use this to copy from temporary mutable structures updated by local
+ validation into the structures that were passed in once the validation
+ has succeeded."""
+ symbol_set_out.clear()
+ symbol_set_out.update(symbol_set_new)
+ output_sequence_out[:] = output_sequence_new
+
+ def _validate (self, symbol_set, output_sequence):
+ """Determine whether an output sequence created from the symbols can
+ be made consistent with the model.
+
+ The symbol set represents letters in an alphabet; the output sequence
+ orders those letters in a way that satisfies the regular expression
+ expressed in the model. Both are changed as a result of a successful
+ validation; both remain unchanged if the validation failed. In
+ recursing, implementers may assume that C{output_sequence} is
+ monotonic: its length remains unchanged after an invocation iff the
+ symbol set also remains unchanged. The L{_validateCloneSymbolSet},
+ L{_validateCloneOutputSequence}, and L{_validateReplaceResults}
+ methods are available to help preserve this behavior.
+
+ @param symbol_set: A map from L{ElementUse} instances to a list of
+ values. The order of the values corresponds to the order in which
+ they should appear. A key of C{None} identifies values that are
+ stored as wildcard elements. Values are removed from the lists as
+ they are used; when the last value of a list is removed, its key is
+ removed from the map. Thus an empty dictionary is the indicator that
+ no more symbols are available.
+
+ @param output_sequence: A mutable list to which should be appended
+ tuples C{( eu, val )} where C{eu} is an L{ElementUse} from the set of
+ symbol keys, and C{val} is a value from the corresponding list. A
+ document generated by producing the elements in the given order is
+ expected to validate.
+
+ @return: C{True} iff the model validates. C{symbol_set} and
+ C{output_path} must be unmodified if returns C{False}.
+ """
+ raise Exception('ContentState_mixin._validate not implemented in %s' % (type(self),))
+
+
class AttributeUse (pyxb.cscRoot):
"""A helper class that encapsulates everything we need to know
about the way an attribute is used within a binding class.
@@ -281,7 +403,7 @@ class AttributeUse (pyxb.cscRoot):
desc.extend(['=', self.__unicodeDefault ])
return ''.join(desc)
-class ElementUse (pyxb.cscRoot):
+class ElementUse (ContentState_mixin, ContentModel_mixin):
"""Aggregate the information relevant to an element of a complex type.
This includes the L{original tag name<name>}, the spelling of L{the
@@ -469,641 +591,54 @@ class ElementUse (pyxb.cscRoot):
desc.append(self.elementBinding()._description(user_documentation=user_documentation))
return ''.join(desc)
-class _DFAState (object):
- """Base class for a suspended DFA interpretation."""
- __contentModel = None
- __state = None
-
- def __init__ (self, content_model, state=1):
- self.__contentModel = content_model
- self.__state = state
-
- def state (self):
- """The current state of the automaton, represented as an integer."""
- return self.__state
- def contentModel (self):
- """The L{ContentModel} to which the state belongs."""
- return self.__contentModel
- def updateState (self, state):
- """Change the automaton state recorded in this DFA state."""
- self.__state = state
+ def newState (self, parent_particle_state):
+ """Implement parent class method."""
return self
- def step (self, dfa_stack, ctd_instance, value, element_use):
- """Execute a step within the content model.
-
- This determines whether the current state in the content model allows
- a transition on the given value. If a transition can be performed,
- the instance element use corresponding to the value is used to record
- the value.
-
- The input value should be an instance of L{basis._TypeBinding_mixin},
- or a value that can be uniquely converted into such a instance using
- the transitions from the current state as clues.
-
- @param dfa_stack: The current state of the parse. Upon return, this may have been augmented with suspended content models.
- @type dfa_stack: L{DFAStack}
- @param value: A value upon which transition should occur.
- @type value: C{xml.dom.Node} or L{basis._TypeBinding_mixin} or other value
- @return: C{True} iff a transition successfully consumed the value
- """
-
- assert isinstance(ctd_instance, basis.complexTypeDefinition)
- self.__state = self.contentModel().step(ctd_instance, self.state(), value, element_use, dfa_stack)
- return self.__state is not None
-
- def isFinal (self):
- """Return C{True} iff the current state of the content model is a final state."""
- return self.contentModel().isFinal(self.state())
-
-class _MGAllState (object):
- """The state of a suspended interpretation of a L{ModelGroupAll}
- transition. This state comprises a set of alternatives, and optionally a
- L{DFAStack} corresponding to the current position within one of the
- alternatives.
- """
-
- __modelGroup = None
- __alternatives = None
- __currentStack = None
- __isFinal = None
-
- def __init__ (self, model_group):
- self.__modelGroup = model_group
- self.__alternatives = self.__modelGroup.alternatives()
-
- def step (self, dfa_stack, ctd_instance, value, element_use):
- """Execute a step within the model group.
-
- If an automaton stack is currently being executed, the step defers to
- that automaton. If a step is succesfully taken, the invocation
- returns; otherwise, the automaton stack is discarded.
-
- If no automaton stack is active, a step is attempted on each automaton
- remaining in the alternatives. If the step is successful, that
- automaton is recorded as being the current one for execution, and the
- invocation returns.
-
- If no automaton can be found within which progress can be made, the
- step fails.
-
- @param dfa_stack: The current state of the parse. Upon return, this
- may have been augmented with suspended content models.
- @type dfa_stack: L{DFAStack}
- @param value: A value upon which transition should occur.
- @type value: C{xml.dom.Node} or L{basis._TypeBinding_mixin} or other value
- @return: C{True} iff a transition was found that consumed the value.
- """
-
- assert isinstance(ctd_instance, basis.complexTypeDefinition)
- if self.__currentStack is not None:
- if self.__currentStack.step(ctd_instance, value, element_use):
- return True
- if not self.__currentStack.isTerminal():
- # I think this is probably a problem, but don't have an
- # example yet to use to analyze it. The issue is that we've
- # already committed to executing the automaton; if we end up
- # in a non-final state, then that execution failed, and
- # probably the whole validation should just abort.
- print '******** Non-terminal state reached in all group parsing; please contact support'
- self.__currentStack = None
- found_match = True
- for alt in self.__alternatives:
- try:
- new_stack = alt.contentModel().initialDFAStack()
- if new_stack.step(ctd_instance, value, element_use):
- self.__currentStack = new_stack
- self.__alternatives.remove(alt)
- return True
- except pyxb.BadDocumentError, e:
- #print 'Failed with alternative %s: %s' % (alt, type(e))
- pass
- return False
-
- def isFinal (self):
- """Return C{True} iff no required alternatives remain."""
- for alt in self.__alternatives:
- # Any required alternative that must consume a symbol prevents
- # this from being an acceptable final state for the model group.
- if alt.required() and not alt.contentModel().allowsEpsilonTransitionToFinal():
- #print "\n\n***Required alternative %s still present\n\n" % (alt,)
- return False
- return True
-
-class DFAStack (object):
- """A stack of states and content models representing the current status of
- an interpretation of a content model, including invocations of nested
- content models reached through L{ModelGroupAll} instances."""
-
- __stack = None
- def __init__ (self, content_model):
- self.__stack = []
- self.pushModelState(_DFAState(content_model))
-
- def pushModelState (self, model_state):
- """Add the given model state as the new top (actively executing) model ."""
- self.__stack.append(model_state)
- return model_state
-
- def isTerminal (self):
- """Return C{True} iff the stack is in a state where the top-level
- model execution has reached a final state."""
- return (0 == len(self.__stack)) or self.topModelState().isFinal()
-
- def popModelState (self):
- """Remove and return the model state currently being executed."""
- if 0 == len(self.__stack):
- raise pyxb.LogicError('Attempt to underflow content model stack')
- return self.__stack.pop()
-
- def topModelState (self):
- """Return a reference to the model state currently being executed.
-
- The state is not removed from the stack."""
- if 0 == len(self.__stack):
- raise pyxb.LogicError('Attempt to underflow content model stack')
- return self.__stack[-1]
-
- def step (self, ctd_instance, value, element_use):
- """Take a step using the value and the current model state.
-
- Execution of the step may add a new model state to the stack.
-
- @return: C{True} iff the value was consumed by a transition."""
- assert isinstance(ctd_instance, basis.complexTypeDefinition)
- if 0 == len(self.__stack):
- return False
- ok = self.topModelState().step(self, ctd_instance, value, element_use)
- if not ok:
- self.popModelState()
- return ok
-
-class ContentModelTransition (pyxb.cscRoot):
- """Represents a transition in the content model DFA.
-
- If the next object in the DOM model conforms to the specified term, it is
- consumed and the specified next state is entered."""
-
- def term (self):
- """The matching term for this transition to succeed."""
- if self.__term is None:
- self.__term = self.__elementUse.elementBinding()
- assert self.__term is not None
- return self.__term
- __term = None
-
- def currentStateRef (self):
- return self.__currentStateRef
- __currentStateRef = None
- def _currentStateRef (self, current_state_ref):
- self.__currentStateRef = current_state_ref
-
- def nextState (self):
- """The next state in the DFA"""
- return self.__nextState
- __nextState = None
-
- # The ElementUse instance used to store a successful match in the
- # complex type definition instance.
- def elementUse (self):
- return self.__elementUse
- __elementUse = None
-
- # Types of transition that can be taken, in order of preferred match
- TT_element = 0x01 #<<< The transition is on an element
- TT_modelGroupAll = 0x02 #<<< The transition is on an ALL model group
- TT_wildcard = 0x03 #<<< The transition is on a wildcard
-
- # What type of term this transition covers
- __termType = None
- def termType (self):
- return self.__termType
-
- def __init__ (self, next_state, element_use=None, term=None):
- """Create a transition to a new state upon receipt of a term,
- storing the successful match using the provided ElementUse."""
- self.__nextState = next_state
- assert self.__nextState is not None
- self.__elementUse = element_use
- if self.__elementUse is not None:
- self.__term = None
- self.__termType = self.TT_element
- else:
- self.__term = term
- assert self.__term is not None
- if isinstance(self.__term, ModelGroupAll):
- self.__termType = self.TT_modelGroupAll
- elif isinstance(self.__term, Wildcard):
- self.__termType = self.TT_wildcard
- else:
- raise pyxb.LogicError('Unexpected transition term %s' % (self.__term,))
-
- def __cmp__ (self, other):
- """Sort transitions so elements precede model groups precede
- wildcards. Also sort within each subsequence."""
- rv = cmp(self.__termType, other.__termType)
- if 0 == rv:
- # In a vain attempt at determinism, sort the element transitions
- # by name
- if (self.TT_element == self.__termType):
- rv = cmp(self.__elementUse.name(), other.__elementUse.name())
- else:
- rv = cmp(self.__term, other.__term)
+ def accepts (self, particle_state, instance, value, element_use):
+ rv = self._accepts(instance, value, element_use)
+ if rv:
+ particle_state.incrementCount()
return rv
- def __processElementTransition (self, value, element_use):
- # First, identify the element
+ def _accepts (self, instance, value, element_use):
+ if element_use == self:
+ self.setOrAppend(instance, value)
+ return True
+ if element_use is not None:
+ # If there's a known element, and it's not this one, the content
+ # does not match. This assumes we handled xsi:type and
+ # substitution groups earlier, which may be true.
+ return False
if isinstance(value, xml.dom.Node):
- # If we got here, it's because we couldn't figure out what element
- # the node conformed to and had to try the element transitions in
- # hopes of matching a wildcard. If we couldn't find an element
- # before, we can't do it now, so just fail.
- return None
+ # If we haven't been able to identify an element for this before,
+ # then we don't recognize it, and will have to treat it as a
+ # wildcard.
+ return False
try:
- # The _convert_string_values=False setting prevents string
- # arguments to element/type constructors from being automatically
- # converted to another type (like int) if they just happen to be
- # convertible. Without this, it's too easy to accept a
- # sub-optimal transition (e.g., match a float when an alternative
- # string is available).
- return self.term().compatibleValue(value, _convert_string_values=False)
+ self.setOrAppend(instance, self.__elementBinding.compatibleValue(value, _convert_string_values=False))
+ return True
except pyxb.BadTypeValueError, e:
- # Silently fail the transition
pass
- return None
-
- def __validateConsume (self, key, available_symbols_im, output_sequence_im, candidates):
- # Update candidates to incorporate the path abstraction associated
- # with the element that is this term.
-
- # Create a mutable copy of the available symbols
- next_symbols = available_symbols_im.copy()
-
- # If the transition is a loop back to the current state, or if the
- # transition is a simple type definition with variety list, we can
- # consume multiple instances. Might as well consume all of them.
- # When we do consume, we can do either one transition, or one
- # transition for each element in a list/vector.
- key_type = type(None)
- elt_plural = False
- if key is not None:
- key_type = key.elementBinding().typeDefinition()
- elt_plural = key.isPlural()
- multiple_values = False
- try:
- iter(next_symbols[key][0])
- multiple_values = True
- except TypeError:
- pass
-
- if (self.__nextState == self.__currentStateRef.state()):
- consume_all = True
- consume_singles = True
- else:
- consume_all = False
- consume_singles = True
- if consume_all:
- consumed = next_symbols[key]
- del next_symbols[key]
- else:
- # Make sure we pop from a copy of the available_symbols_im entry value.
- next_left = next_symbols[key][:]
- consumed = [ next_left.pop(0) ]
- if 0 == len(next_left):
- del next_symbols[key]
- else:
- next_symbols[key] = next_left
- if consume_singles:
- output_sequence = output_sequence_im + [ (key, _c) for _c in consumed ]
- else:
- output_sequence = output_sequence_im + [ (key, key_type(consumed)) ]
- assert (not (key in next_symbols)) or (0 < len(next_symbols[key]))
- candidate = (self.__nextState, next_symbols, output_sequence)
- candidates.append(candidate)
- return True
-
- def validate (self, available_symbols_im, output_sequence_im, candidates):
- """Determine whether it is possible to take this transition using the
- available symbols.
-
- @param available_symbols_im: As with L{ContentModel.validate}. The
- map will not be modified by this method.
-
- @param output_sequence_im: As with the return value of
- L{ContentModel.validate}. The sequence will not be modified by this
- event (it is used as a prefix for new candidates).
-
- @param candidates: A list of candidate validation paths.
-
- @return: C{True} iff the transition could be made."""
- if self.TT_element == self.__termType:
- if not (self.__elementUse in available_symbols_im):
- # No symbol available for this transition
- return False
- assert 0 < len(available_symbols_im[self.__elementUse])
- return self.__validateConsume(self.__elementUse, available_symbols_im, output_sequence_im, candidates)
- elif self.TT_modelGroupAll == self.__termType:
- return self.term().validate(available_symbols_im, output_sequence_im, self.__nextState, candidates)
- elif self.TT_wildcard == self.__termType:
- if not (None in available_symbols_im):
- return False
- assert 0 < len(available_symbols_im[None])
- return self.__validateConsume(None, available_symbols_im, output_sequence_im, candidates)
+ #print '%s %s %s in %s' % (instance, value, element_use, self)
return False
- def allowsEpsilonTransition (self):
- """Determine whether it is possible to take this transition without
- consuming any symbols.
-
- This is only possible if this is a transition to a final state using
- an "all" model group for which every alternative is effectively
- optional.
- """
- if self.TT_modelGroupAll != self.__termType:
+ def _validate (self, symbol_set, output_sequence):
+ values = symbol_set.get(self)
+ #print 'values %s' % (values,)
+ if values is None:
return False
- dfa_state = _MGAllState(self.__term)
- return dfa_state.isFinal()
-
- def attemptTransition (self, ctd_instance, value, element_use, dfa_stack):
- """Attempt to make the appropriate transition.
-
- @param ctd_instance: The binding instance for which we are attempting
- to set values by walking the content model.
- @type ctd_instance: L{basis.complexTypeDefinition}
-
- @param value: The potential value that would be consumed if this
- transition can be made.
- @type value: C{xml.dom.Node} or L{basis._TypeBinding_mixin}
-
- @param dfa_stack: The current state of processing this and enclosing
- content models.
- @type dfa_stack: L{DFAStack}
-
- @return: C{True} iff C{value} is acceptable for this transition
-
- """
-
- if self.TT_element == self.__termType:
- element = None
- # If the element use matched one of the terms in its state, we
- # would never have gotten here, so don't even try. We're only
- # walking the terms to see if an ALL or Wildcard is allowed.
- if (element_use is not None):
- return None
- element = self.__processElementTransition(value, element_use)
- if element is None:
- return False
- self.__elementUse.setOrAppend(ctd_instance, element)
- return True
- if self.TT_modelGroupAll == self.__termType:
- return dfa_stack.pushModelState(_MGAllState(self.__term)).step(dfa_stack, ctd_instance, value, element_use)
- if self.TT_wildcard == self.__termType:
- value_desc = 'value of type %s' % (type(value),)
- if isinstance(value, xml.dom.Node):
- value_desc = 'DOM node %s' % (pyxb.namespace.ExpandedName(value),)
- elif not isinstance(value, basis._TypeBinding_mixin):
- return False
- if not self.__term.matches(ctd_instance, value):
- raise pyxb.UnexpectedContentError(value)
- if not isinstance(value, basis._TypeBinding_mixin):
- print 'NOTE: Created unbound wildcard element from %s' % (value_desc,)
- assert isinstance(ctd_instance.wildcardElements(), list), 'Uninitialized wildcard list in %s' % (ctd_instance._ExpandedName,)
- ctd_instance._appendWildcardElement(value)
- return True
- raise pyxb.LogicError('Unexpected transition term %s' % (self.__term,))
-
-class ContentModelState (pyxb.cscRoot):
- """Represents a state in a ContentModel DFA.
-
- The state identifier is an integer. State 1 is the starting state of the
- DFA. A flag indicates whether the state is a legitimate final state for
- the DFA. The transitions are an ordered sequence of
- ContentModelTransition instances."""
-
- # Integer
- __state = None
- # Sequence of ContentModelTransition instances
- __transitions = None
-
- # Map from ElementUse instances to the term that transitions on that use.
- __elementTermMap = None
-
- def isFinal (self):
- """If True, this state can successfully complete the element
- reduction."""
- return self.__isFinal
- __isFinal = None
-
- def state (self):
- return self.__state
-
- def __init__ (self, state, is_final, transitions):
- self.__state = state
- self.__isFinal = is_final
- self.__transitions = transitions
- [ _t._currentStateRef(self) for _t in self.__transitions ]
- self.__transitions.sort()
- self.__elementTermMap = { }
- for t in self.__transitions:
- if t.TT_element == t.termType():
- assert t.elementUse() is not None
- self.__elementTermMap[t.elementUse()] = t
-
- def transitions (self):
- return self.__transitions
-
- def allowsEpsilonTransitionToFinal (self, content_model):
- """Determine can reach a final state in the content model without
- consuming anything."""
- if self.isFinal():
- return True
- for transition in self.__transitions:
- if transition.allowsEpsilonTransition() and content_model.isFinal(transition.nextState()):
- return True
- return False
-
- def evaluateContent (self, ctd_instance, value, element_use, dfa_stack):
- """Try to make a single transition with the given value.
-
- @param ctd_instance: The binding instance for which we are attempting
- to set values by walking the content model.
- @type ctd_instance: L{basis.complexTypeDefinition}
-
- @param value: The value that would be consumed if a transition can be
- made.
- @type value: C{xml.dom.Node} or L{basis._TypeBinding_mixin}
-
- @param element_use: The L{ElementUse<pyxb.binding.content.ElementUse>}
- corresponding to the provided value, if known (for example, because
- the value was parsed from an XML document).
-
- @param dfa_stack: The current state of processing this and enclosing
- content models.
- @type dfa_stack: L{DFAStack}
-
- @return: If a transition could be taken, the next state in the content
- model. C{None} if no transition could be taken and this state is
- final.
-
- @raise pyxb.UnrecognizedContentError: No transition on C{value} is
- possible, and this is not a final state.
- """
-
- if element_use is not None:
- transition = self.__elementTermMap.get(element_use)
- if transition is not None:
- element_use.setOrAppend(ctd_instance, value)
- return transition.nextState()
- # Might get here legitimately if we need to descend into ALL, or
- # if this value is a wildcard for which we happen to have a
- # binding class available.
- for transition in self.__transitions:
- if transition.attemptTransition(ctd_instance, value, element_use, dfa_stack):
- return transition.nextState()
- if self.isFinal():
- return None
- raise pyxb.UnrecognizedContentError(value, element_use=element_use)
-
-class ContentModel (pyxb.cscRoot):
- """The ContentModel is a deterministic finite state automaton which can be
- traversed using a sequence of DOM nodes which are matched on transitions
- against the legal content model of a complex type."""
-
- # Map from integers to ContentModelState instances
- __stateMap = None
-
- # All DFAs begin at this state
- __InitialState = 1
-
- def __init__ (self, state_map=None):
- self.__stateMap = state_map
-
- def initialDFAStack (self):
- return DFAStack(self)
-
- def step (self, ctd_instance, state, value, element_use, dfa_stack):
- """Perform a single step in the content model. This is a pass-through
- to L{ContentModelState.evaluateContent} for the appropriate state.
-
- @param state: The starting state in this content model.
- @type state: C{int}
- """
-
- return self.__stateMap[state].evaluateContent(ctd_instance, value, element_use, dfa_stack)
-
- def isFinal (self, state):
- return self.__stateMap[state].allowsEpsilonTransitionToFinal(self)
-
- def allowsEpsilonTransitionToFinal (self):
- return self.__stateMap[self.__InitialState].allowsEpsilonTransitionToFinal(self)
-
- def validate (self, available_symbols, succeed_at_dead_end=False):
- """Determine whether this content model can be satisfied using the
- provided elements.
-
- The general idea is to treat the transitions of the DFA as symbols in
- an alphabet. For each such transition, a sequence of values is
- provided to be associated with the transition. One transition is
- permitted for each value associated with the symbol. The symbol (key)
- C{None} represents wildcard values.
-
- If a path is found that uses every symbol in valid transitions and
- ends in a final state, the return value is a pair consisting of the
- unconsumed symbols and a sequence of term, value pairs that define the
- acceptable path. If no valid path through the DFA can be taken,
- C{None} is returned.
-
- @param available_symbols: A map from leaf DFA terms to a sequence of
- values associated with the term in a binding instance. The key
- C{None} is used to represent wildcard elements. If a key appears in
- this map, it must have at least one value in its sequence.
-
- @param succeed_at_dead_end: If C{True}, states from which no
- transition can be made are accepted as final states. This is used
- when processing "all" model groups, where the content model for the
- current alternative must succeed while retaining the symbols that are
- needed for other alternatives.
- """
-
- candidates = []
- candidates.append( (1, available_symbols, []) )
- while 0 < len(candidates):
- (state_id, symbols, sequence) = candidates.pop(0)
- state = self.__stateMap[state_id]
- if 0 == len(symbols):
- # No symbols available for transitions in this state. If this
- # places us in a final state, we've got a successful path and
- # should return it. Otherwise, this path failed, and we go on
- # to the next candidate.
- if state.allowsEpsilonTransitionToFinal(self):
- return (symbols, sequence)
- continue
- # Collect all the alternatives that are possible by taking
- # transitions from this state.
- num_transitions = 0
- for transition in state.transitions():
- num_transitions += transition.validate(symbols, sequence, candidates)
- if (0 == num_transitions) and succeed_at_dead_end:
- return (symbols, sequence)
- return None
-
-class ModelGroupAllAlternative (pyxb.cscRoot):
- """Represents a single alternative in an "all" model group."""
-
- def contentModel (self):
- """The content model definition for the alternative."""
- return self.__contentModel
- __contentModel = None
-
- def required (self):
- """True iff this alternative must be present (min_occurs=1)"""
- return self.__required
- __required = None
+ used = values.pop(0)
+ output_sequence.append( (self, used) )
+ if 0 == len(values):
+ del symbol_set[self]
+ return True
- def __init__ (self, content_model, required):
- #print '%s created MGA alternative model %s required %s' % (self, content_model, required)
- self.__contentModel = content_model
- self.__required = required
+ def __str__ (self):
+ return 'EU.%s@%x' % (self.__name, id(self))
-class ModelGroupAll (pyxb.cscRoot):
- """Content model class that represents a ModelGroup with an "all"
- compositor."""
-
- __alternatives = None
- def alternatives (self):
- return set(self.__alternatives)
-
- def __init__ (self, alternatives):
- self.__alternatives = alternatives
-
- def validate (self, available_symbols_im, output_sequence_im, next_state, candidates):
- num_matches = 0
- alternatives = set(self.__alternatives)
- symbols = available_symbols_im
- output_sequence = output_sequence_im[:]
- found_match = True
- while (0 < len(alternatives)) and found_match:
- found_match = False
- for alt in alternatives:
- path = alt.contentModel().validate(symbols, succeed_at_dead_end=True)
- if path is None:
- break
- (new_symbols, new_sequence) = path
- found_match = (0 < len(new_sequence))
- if found_match:
- output_sequence.extend(new_sequence)
- symbols = new_symbols
- alternatives.remove(alt)
- found_match = True
- break
- for alt in alternatives:
- if alt.required():
- return False
- candidates.append( (next_state, symbols, output_sequence) )
- return True
-
-class Wildcard (pyxb.cscRoot):
+class Wildcard (ContentState_mixin, ContentModel_mixin):
"""Placeholder for wildcard objects."""
NC_any = '##any' #<<< The namespace constraint "##any"
@@ -1134,21 +669,467 @@ class Wildcard (pyxb.cscRoot):
__processContents = None
def processContents (self): return self.__processContents
+ def __normalizeNamespace (self, nsv):
+ if nsv is None:
+ return None
+ if isinstance(nsv, basestring):
+ nsv = pyxb.namespace.NamespaceForURI(nsv, create_if_missing=True)
+ assert isinstance(nsv, pyxb.namespace.Namespace), 'unexpected non-namespace %s' % (nsv,)
+ return nsv
+
def __init__ (self, *args, **kw):
# Namespace constraint and process contents are required parameters.
- self.__namespaceConstraint = kw['namespace_constraint']
+ nsc = kw['namespace_constraint']
+ if isinstance(nsc, tuple):
+ nsc = (nsc[0], self.__normalizeNamespace(nsc[1]))
+ elif isinstance(nsc, set):
+ nsc = set([ self.__normalizeNamespace(_uri) for _uri in nsc ])
+ self.__namespaceConstraint = nsc
self.__processContents = kw['process_contents']
- def matches (self, ctd_instance, value):
+ def matches (self, instance, value):
"""Return True iff the value is a valid match against this wildcard.
- Not implemented yet: all wildcards are assumed to match all values.
-
+ Validation per U{Wildcard allows Namespace Name<http://www.w3.org/TR/xmlschema-1/#cvc-wildcard-namespace>}.
"""
+
+ ns = None
+ if isinstance(value, xml.dom.Node):
+ if value.namespaceURI is not None:
+ ns = pyxb.namespace.NamespaceForURI(value.namespaceURI)
+ elif isinstance(value, basis._TypeBinding_mixin):
+ elt = value._element()
+ if elt is not None:
+ ns = elt.name().namespace()
+ else:
+ ns = value._ExpandedName.namespace()
+ else:
+ raise pyxb.LogicError('Need namespace from value')
+ if isinstance(ns, pyxb.namespace.Namespace) and ns.isAbsentNamespace():
+ ns = None
+ if self.NC_any == self.__namespaceConstraint:
+ return True
+ if isinstance(self.__namespaceConstraint, tuple):
+ (_, constrained_ns) = self.__namespaceConstraint
+ assert self.NC_not == _
+ if ns is None:
+ return False
+ if constrained_ns == ns:
+ return False
+ return True
+ return ns in self.__namespaceConstraint
+
+ def newState (self, parent_particle_state):
+ return self
+
+ def accepts (self, particle_state, instance, value, element_use):
+ if isinstance(value, xml.dom.Node):
+ value_desc = 'value in %s' % (value.nodeName,)
+ else:
+ value_desc = 'value of type %s' % (type(value),)
+ if not self.matches(instance, value):
+ return False
+ if not isinstance(value, basis._TypeBinding_mixin):
+ print 'NOTE: Created unbound wildcard element from %s' % (value_desc,)
+ assert isinstance(instance.wildcardElements(), list), 'Uninitialized wildcard list in %s' % (instance._ExpandedName,)
+ instance._appendWildcardElement(value)
+ particle_state.incrementCount()
+ return True
+
+ def _validate (self, symbol_set, output_sequence):
# @todo check node against namespace constraint and process contents
#print 'WARNING: Accepting node as wildcard match without validating.'
+ wc_values = symbol_set.get(None)
+ if wc_values is None:
+ return False
+ used = wc_values.pop(0)
+ output_sequence.append( (None, used) )
+ if 0 == len(wc_values):
+ del symbol_set[None]
return True
+class SequenceState (ContentState_mixin):
+ __failed = False
+ __satisfied = False
+
+ def __init__ (self, group, parent_particle_state):
+ super(SequenceState, self).__init__(group)
+ self.__sequence = group
+ self.__parentParticleState = parent_particle_state
+ self.__particles = group.particles()
+ self.__index = -1
+ self.__satisfied = False
+ self.__failed = False
+ self.notifyFailure(None, False)
+ #print 'SS.CTOR %s: %d elts' % (self, len(self.__particles))
+
+ def accepts (self, particle_state, instance, value, element_use):
+ assert self.__parentParticleState == particle_state
+ assert not self.__failed
+ #print 'SS.ACC %s: %s %s %s' % (self, instance, value, element_use)
+ while self.__particleState is not None:
+ (consume, underflow_exc) = self.__particleState.step(instance, value, element_use)
+ if consume:
+ return True
+ if underflow_exc is not None:
+ self.__failed = True
+ raise underflow_exc
+ return False
+
+ def _verifyComplete (self, parent_particle_state):
+ while self.__particleState is not None:
+ self.__particleState.verifyComplete()
+
+ def notifyFailure (self, sub_state, particle_ok):
+ self.__index += 1
+ self.__particleState = None
+ if self.__index < len(self.__particles):
+ self.__particleState = ParticleState(self.__particles[self.__index], self)
+ else:
+ self.__satisfied = particle_ok
+ if particle_ok:
+ self.__parentParticleState.incrementCount()
+ #print 'SS.NF %s: %d %s %s' % (self, self.__index, particle_ok, self.__particleState)
+
+class ChoiceState (ContentState_mixin):
+ def __init__ (self, group, parent_particle_state):
+ self.__parentParticleState = parent_particle_state
+ super(ChoiceState, self).__init__(group)
+ self.__choices = [ ParticleState(_p, self) for _p in group.particles() ]
+ self.__activeChoice = None
+ #print 'CS.CTOR %s: %d choices' % (self, len(self.__choices))
+
+ def accepts (self, particle_state, instance, value, element_use):
+ #print 'CS.ACC %s %s: %s %s %s' % (self, self.__activeChoice, instance, value, element_use)
+ if self.__activeChoice is None:
+ for choice in self.__choices:
+ #print 'CS.ACC %s candidate %s' % (self, choice)
+ try:
+ (consume, underflow_exc) = choice.step(instance, value, element_use)
+ except Exception, e:
+ consume = False
+ underflow_exc = e
+ #print 'CS.ACC %s: candidate %s : %s' % (self, choice, consume)
+ if consume:
+ self.__activeChoice = choice
+ self.__choices = None
+ return True
+ return False
+ (consume, underflow_exc) = self.__activeChoice.step(instance, value, element_use)
+ #print 'CS.ACC %s : active choice %s %s %s' % (self, self.__activeChoice, consume, underflow_exc)
+ if consume:
+ return True
+ if underflow_exc is not None:
+ self.__failed = True
+ raise underflow_exc
+ return False
+
+ def _verifyComplete (self, parent_particle_state):
+ rv = True
+ #print 'CS.VC %s: %s' % (self, self.__activeChoice)
+ if self.__activeChoice is None:
+ # Use self.__activeChoice as the iteration value so that it's
+ # non-None when notifyFailure is invoked.
+ for self.__activeChoice in self.__choices:
+ try:
+ #print 'CS.VC: try %s' % (self.__activeChoice,)
+ self.__activeChoice.verifyComplete()
+ return
+ except Exception, e:
+ pass
+ #print 'Missing components %s' % ("\n".join([ "\n ".join([str(_p2.term()) for _p2 in _p.particle().term().particles()]) for _p in self.__choices ]),)
+ raise pyxb.MissingContentError('choice')
+ self.__activeChoice.verifyComplete()
+
+ def notifyFailure (self, sub_state, particle_ok):
+ #print 'CS.NF %s %s' % (self, particle_ok)
+ if particle_ok and (self.__activeChoice is not None):
+ self.__parentParticleState.incrementCount()
+ pass
+
+class AllState (ContentState_mixin):
+ __activeChoice = None
+ __needRetry = False
+ def __init__ (self, group, parent_particle_state):
+ self.__parentParticleState = parent_particle_state
+ super(AllState, self).__init__(group)
+ self.__choices = set([ ParticleState(_p, self) for _p in group.particles() ])
+ #print 'AS.CTOR %s: %d choices' % (self, len(self.__choices))
+
+ def accepts (self, particle_state, instance, value, element_use):
+ #print 'AS.ACC %s %s: %s %s %s' % (self, self.__activeChoice, instance, value, element_use)
+ self.__needRetry = True
+ while self.__needRetry:
+ self.__needRetry = False
+ if self.__activeChoice is None:
+ for choice in self.__choices:
+ #print 'AS.ACC %s candidate %s' % (self, choice)
+ try:
+ (consume, underflow_exc) = choice.step(instance, value, element_use)
+ except Exception, e:
+ consume = False
+ underflow_exc = e
+ #print 'AS.ACC %s: candidate %s : %s' % (self, choice, consume)
+ if consume:
+ self.__activeChoice = choice
+ self.__choices.discard(self.__activeChoice)
+ return True
+ return False
+ (consume, underflow_exc) = self.__activeChoice.step(instance, value, element_use)
+ #print 'AS.ACC %s : active choice %s %s %s' % (self, self.__activeChoice, consume, underflow_exc)
+ if consume:
+ return True
+ if underflow_exc is not None:
+ self.__failed = True
+ raise underflow_exc
+ return False
+
+ def _verifyComplete (self, parent_particle_state):
+ #print 'AS.VC %s: %s, %d left' % (self, self.__activeChoice, len(self.__choices))
+ if self.__activeChoice is not None:
+ self.__activeChoice.verifyComplete()
+ while self.__choices:
+ self.__activeChoice = self.__choices.pop()
+ self.__activeChoice.verifyComplete()
+
+ def notifyFailure (self, sub_state, particle_ok):
+ #print 'AS.NF %s %s' % (self, particle_ok)
+ self.__needRetry = True
+ self.__activeChoice = None
+ if particle_ok and (0 == len(self.__choices)):
+ self.__parentParticleState.incrementCount()
+
+class ParticleState (pyxb.cscRoot):
+ def __init__ (self, particle, parent_state=None):
+ self.__particle = particle
+ self.__parentState = parent_state
+ self.__count = -1
+ #print 'PS.CTOR %s: particle %s' % (self, particle)
+ self.incrementCount()
+
+ def particle (self): return self.__particle
+
+ def incrementCount (self):
+ #print 'PS.IC %s' % (self,)
+ self.__count += 1
+ self.__termState = self.__particle.term().newState(self)
+ self.__tryAccept = True
+
+ def verifyComplete (self):
+ # @TODO@ Set a flag so we can make verifyComplete safe to call
+ # multiple times?
+ #print 'PS.VC %s entry' % (self,)
+ if not self.__particle.satisfiesOccurrences(self.__count):
+ self.__termState._verifyComplete(self)
+ if not self.__particle.satisfiesOccurrences(self.__count):
+ print 'PS.VC %s incomplete' % (self,)
+ raise pyxb.MissingContentError('incomplete')
+ if self.__parentState is not None:
+ self.__parentState.notifyFailure(self, True)
+
+ def step (self, instance, value, element_use):
+ """Attempt to apply the value as a new instance of the particle's term.
+
+ The L{ContentState_mixin} created for the particle's term is consulted
+ to determine whether the instance can accept the given value. If so,
+ the particle's maximum occurrence limit is checked; if not, and the
+ particle has a parent state, it is informed of the failure.
+
+ @param instance: An instance of a subclass of
+ {basis.complexTypeDefinition}, into which the provided value will be
+ stored if it is consistent with the current model state.
+
+ @param value: The value that is being validated against the state.
+
+ @param element_use: An optional L{ElementUse} instance that specifies
+ the element to which the value corresponds. This will be available
+ when the value is extracted by parsing a document, but will be absent
+ if the value was passed as a constructor positional parameter.
+
+ @return: C{( consumed, underflow_exc )} A tuple where the first element
+ is C{True} iff the provided value was accepted in the current state.
+ When this first element is C{False}, the second element will be
+ C{None} if the particle's occurrence requirements have been met, and
+ is an instance of C{MissingElementError} if the observed number of
+ terms is less than the minimum occurrence count. Depending on
+ context, the caller may raise this exception, or may try an
+ alternative content model.
+
+ @raise pyxb.UnexpectedElementError: if the value satisfies the particle,
+ but having done so exceeded the allowable number of instances of the
+ term.
+ """
+
+ #print 'PS.STEP %s: %s %s %s' % (self, instance, value, element_use)
+
+ # Only try if we're not already at the upper limit on occurrences
+ consumed = False
+ underflow_exc = None
+
+ # We can try the value against the term if we aren't at the maximum
+ # count for the term. Also, if we fail to consume, but as a side
+ # effect of the test the term may have reset itself, we can try again.
+ self.__tryAccept = True
+ while self.__tryAccept and (self.__count != self.__particle.maxOccurs()):
+ self.__tryAccept = False
+ consumed = self.__termState.accepts(self, instance, value, element_use)
+ #print 'PS.STEP %s: ta %s %s' % (self, self.__tryAccept, consumed)
+ self.__tryAccept = self.__tryAccept and (not consumed)
+ #print 'PS.STEP %s: %s' % (self, consumed)
+ if consumed:
+ if not self.__particle.meetsMaximum(self.__count):
+ raise pyxb.UnexpectedElementError('too many')
+ else:
+ if self.__parentState is not None:
+ self.__parentState.notifyFailure(self, self.__particle.satisfiesOccurrences(self.__count))
+ if not self.__particle.meetsMinimum(self.__count):
+ # @TODO@ Use better exception; changing this will require
+ # changing some unit tests.
+ #underflow_exc = pyxb.MissingElementError('too few')
+ underflow_exc = pyxb.UnrecognizedContentError('too few')
+ return (consumed, underflow_exc)
+
+ def __str__ (self):
+ particle = self.__particle
+ return 'ParticleState(%d:%d,%s:%s)@%x' % (self.__count, particle.minOccurs(), particle.maxOccurs(), particle.term(), id(self))
+
+class ParticleModel (ContentModel_mixin):
+ """Content model dealing with particles: terms with occurrence restrictions"""
+
+ def minOccurs (self): return self.__minOccurs
+ def maxOccurs (self): return self.__maxOccurs
+ def term (self): return self.__term
+
+ def meetsMaximum (self, count):
+ """@return: C{True} iff there is no maximum on term occurrence, or the
+ provided count does not exceed that maximum"""
+ return (self.__maxOccurs is None) or (count <= self.__maxOccurs)
+
+ def meetsMinimum (self, count):
+ """@return: C{True} iff the provided count meets the minimum number of
+ occurrences"""
+ return count >= self.__minOccurs
+
+ def satisfiesOccurrences (self, count):
+ """@return: C{True} iff the provided count satisfies the occurrence
+ requirements"""
+ return self.meetsMinimum(count) and self.meetsMaximum(count)
+
+ def __init__ (self, term, min_occurs=1, max_occurs=1):
+ self.__term = term
+ self.__minOccurs = min_occurs
+ self.__maxOccurs = max_occurs
+
+ def newState (self):
+ return ParticleState(self)
+
+ def validate (self, symbol_set):
+ """Determine whether the particle requirements are satisfiable by the
+ given symbol set.
+
+ The symbol set represents letters in an alphabet. If those letters
+ can be combined in a way that satisfies the regular expression
+ expressed in the model, a satisfying sequence is returned and the
+ symbol set is reduced by the letters used to form the sequence. If
+ the content model cannot be satisfied, C{None} is returned and the
+ symbol set remains unchanged.
+
+ @param symbol_set: A map from L{ElementUse} instances to a list of
+ values. The order of the values corresponds to the order in which
+ they should appear. A key of C{None} identifies values that are
+ stored as wildcard elements. Values are removed from the lists as
+ they are used; when the last value of a list is removed, its key is
+ removed from the map. Thus an empty dictionary is the indicator that
+ no more symbols are available.
+
+ @return: returns C{None}, or a list of tuples C{( eu, val )} where
+ C{eu} is an L{ElementUse} from the set of symbol keys, and C{val} is a
+ value from the corresponding list.
+ """
+
+ output_sequence = []
+ #print 'Start: %d %s %s : %s' % (self.__minOccurs, self.__maxOccurs, self.__term, symbol_set)
+ result = self._validate(symbol_set, output_sequence)
+ #print 'End: %s %s %s' % (result, symbol_set, output_sequence)
+ if result:
+ return (symbol_set, output_sequence)
+ return None
+
+ def _validate (self, symbol_set, output_sequence):
+ symbol_set_mut = self._validateCloneSymbolSet(symbol_set)
+ output_sequence_mut = self._validateCloneOutputSequence(output_sequence)
+ count = 0
+ #print 'VAL start %s: %d %s' % (self.__term, self.__minOccurs, self.__maxOccurs)
+ last_size = len(output_sequence_mut)
+ while (count != self.__maxOccurs) and self.__term._validate(symbol_set_mut, output_sequence_mut):
+ #print 'VAL %s old cnt %d, left %s' % (self.__term, count, symbol_set_mut)
+ this_size = len(output_sequence_mut)
+ if this_size == last_size:
+ # Validated without consuming anything. Assume we can
+ # continue to do so, jump to the minimum, and exit.
+ if count < self.__minOccurs:
+ count = self.__minOccurs
+ break
+ count += 1
+ last_size = this_size
+ result = self.satisfiesOccurrences(count)
+ if (result):
+ self._validateReplaceResults(symbol_set, symbol_set_mut, output_sequence, output_sequence_mut)
+ #print 'VAL end PRT %s res %s: %s %s %s' % (self.__term, result, self.__minOccurs, count, self.__maxOccurs)
+ return result
+
+class _Group (ContentModel_mixin):
+ """Base class for content information pertaining to a U{model
+ group<http://www.w3.org/TR/xmlschema-1/#Model_Groups>}.
+
+ There is a specific subclass for each group compositor.
+ """
+
+ _StateClass = None
+ """A reference to a L{ContentState_mixin} class that maintains state when
+ validating an instance of this group."""
+
+ def particles (self): return self.__particles
+
+ def __init__ (self, *particles):
+ self.__particles = particles
+
+ def newState (self, parent_particle_state):
+ return self._StateClass(self, parent_particle_state)
+
+ # All and Sequence share the same validation code, so it's up here.
+ def _validate (self, symbol_set, output_sequence):
+ symbol_set_mut = self._validateCloneSymbolSet(symbol_set)
+ output_sequence_mut = self._validateCloneOutputSequence(output_sequence)
+ for p in self.particles():
+ if not p._validate(symbol_set_mut, output_sequence_mut):
+ return False
+ self._validateReplaceResults(symbol_set, symbol_set_mut, output_sequence, output_sequence_mut)
+ return True
+
+
+class GroupChoice (_Group):
+ _StateClass = ChoiceState
+
+ # Choice requires a different validation algorithm
+ def _validate (self, symbol_set, output_sequence):
+ reset_mutables = True
+ for p in self.particles():
+ if reset_mutables:
+ symbol_set_mut = self._validateCloneSymbolSet(symbol_set)
+ output_sequence_mut = self._validateCloneOutputSequence(output_sequence)
+ if p._validate(symbol_set_mut, output_sequence_mut):
+ self._validateReplaceResults(symbol_set, symbol_set_mut, output_sequence, output_sequence_mut)
+ return True
+ reset_mutables = len(output_sequence) != len(output_sequence_mut)
+ return False
+
+class GroupAll (_Group):
+ _StateClass = AllState
+
+class GroupSequence (_Group):
+ _StateClass = SequenceState
+
## Local Variables:
## fill-column:78
## End:
diff --git a/pyxb/binding/datatypes.py b/pyxb/binding/datatypes.py
index 32e476f..f5a1a6e 100644
--- a/pyxb/binding/datatypes.py
+++ b/pyxb/binding/datatypes.py
@@ -847,8 +847,8 @@ class normalizedString (string):
# input, or the _InvalidRE class variable to a compiled regular
# expression that detects invalid inputs.
#
- # Alternatively, subclasses can override the _ValidateString
- # class.
+ # Alternatively, subclasses can override the _ValidateString_va
+ # method.
_ExpandedName = pyxb.namespace.XMLSchema.createExpandedName('normalizedString')
@@ -1072,9 +1072,7 @@ class anyType (basis.complexTypeDefinition):
_AttributeWildcard = content.Wildcard(namespace_constraint=content.Wildcard.NC_any, process_contents=content.Wildcard.PC_lax)
# Generate from tests/schemas/anyType.xsd
- _ContentModel = content.ContentModel(state_map = {
- 1 : content.ContentModelState(state=1, is_final=True, transitions=[
- content.ContentModelTransition(term=content.Wildcard(process_contents=content.Wildcard.PC_lax, namespace_constraint=content.Wildcard.NC_any), next_state=1, element_use=None),
- ])
-})
+ __Wildcard = content.Wildcard(process_contents=content.Wildcard.PC_lax, namespace_constraint=content.Wildcard.NC_any)
+ __Inner = content.GroupSequence(content.ParticleModel(__Wildcard, min_occurs=0, max_occurs=None))
+ _ContentModel = content.ParticleModel(__Inner, min_occurs=1, max_occurs=1)
diff --git a/pyxb/binding/generate.py b/pyxb/binding/generate.py
index c87bd5a..b1111f5 100644
--- a/pyxb/binding/generate.py
+++ b/pyxb/binding/generate.py
@@ -31,8 +31,6 @@ import content
import datatypes
import facets
-import nfa
-
import types
import sys
import traceback
@@ -114,7 +112,7 @@ class ReferenceWildcard (ReferenceLiteral):
namespaces.append(None)
else:
namespaces.append(ns.uri())
- template_map['nc'] = 'set(%s)' % (",".join( [ repr(_ns) for _ns in namespaces ]))
+ template_map['nc'] = 'set([%s])' % (",".join( [ repr(_ns) for _ns in namespaces ]))
else:
assert isinstance(wildcard.namespaceConstraint(), tuple)
ns = wildcard.namespaceConstraint()[1]
@@ -285,72 +283,45 @@ def pythonLiteral (value, **kw):
return str(value)
-def GenerateModelGroupAll (ctd, mga, binding_module, template_map, **kw):
- mga_tag = '__AModelGroup'
- template_map['mga_tag'] = mga_tag
+def GenerateContentTerm (ctd, term, binding_module, **kw):
lines = []
- lines2 = []
- for ( dfa, is_required ) in mga.particles():
- ( dfa_tag, dfa_lines ) = GenerateContentModel(ctd, dfa, binding_module, **kw)
- lines.extend(dfa_lines)
- template_map['dfa_tag'] = dfa_tag
- template_map['is_required'] = binding_module.literal(is_required, **kw)
- lines2.append(templates.replaceInText(' %{content}.ModelGroupAllAlternative(%{ctd}.%{dfa_tag}, %{is_required}),', **template_map))
- lines.append(templates.replaceInText('%{mga_tag} = %{content}.ModelGroupAll(alternatives=[', **template_map))
- lines.extend(lines2)
- lines.append('])')
- return (mga_tag, lines)
-
-def GenerateContentModel (ctd, automaton, binding_module, **kw):
- cmi = None
+ padding = ' '
+ separator = ",\n%s" % (padding,)
+ template_map = { 'ctd' : binding_module.literal(ctd, **kw) }
+ if isinstance(term, xs.structures.Wildcard):
+ term_val = binding_module.literal(term, **kw)
+ elif isinstance(term, xs.structures.ElementDeclaration):
+ term_val = templates.replaceInText('%{ctd}._UseForTag(%{field_tag})', field_tag=binding_module.literal(term.expandedName(), **kw), **template_map)
+ else:
+ gm_id = utility.PrepareIdentifier('GroupModel', binding_module.uniqueInClass(ctd), protected=True)
+ assert isinstance(term, xs.structures.ModelGroup)
+ if (term.C_ALL == term.compositor()):
+ group_val = 'All'
+ elif (term.C_CHOICE == term.compositor()):
+ group_val = 'Choice'
+ else:
+ assert term.C_SEQUENCE == term.compositor()
+ group_val = 'Sequence'
+ pvalues = []
+ for p in term.particles():
+ (value, plines) = GenerateContentParticle(ctd, p, binding_module, **kw)
+ if plines:
+ lines.extend(plines)
+ pvalues.append(value)
+ group_val = "pyxb.binding.content.Group%s(\n" % (group_val,) + padding + separator.join(pvalues) + "\n" + padding + ")"
+ template_map['gm_id'] = gm_id
+ lines.append(templates.replaceInText('%{ctd}.%{gm_id} = %{group_val}', group_val=group_val, **template_map))
+ term_val = templates.replaceInText('%{ctd}.%{gm_id}', **template_map)
+ return (term_val, lines)
+
+def GenerateContentParticle (ctd, particle, binding_module, **kw):
template_map = { }
template_map['ctd'] = binding_module.literal(ctd, **kw)
- try:
- cmi = '_ContentModel_%d' % (ctd.__contentModelIndex,)
- ctd.__contentModelIndex += 1
- except AttributeError:
- cmi = '_ContentModel'
- ctd.__contentModelIndex = 1
- template_map['cm_tag'] = cmi
- template_map['content'] = 'pyxb.binding.content'
- template_map['state_comma'] = ' '
- lines = []
- lines2 = []
- for (state, transitions) in automaton.items():
- if automaton.end() == state:
- continue
- template_map['state'] = binding_module.literal(state)
- template_map['is_final'] = binding_module.literal(None in transitions)
-
- lines2.append(templates.replaceInText('%{state_comma} %{state} : %{content}.ContentModelState(state=%{state}, is_final=%{is_final}, transitions=[', **template_map))
- template_map['state_comma'] = ','
- lines3 = []
- for (key, destinations) in transitions.items():
- if key is None:
- continue
- assert 1 == len(destinations)
- template_map['next_state'] = binding_module.literal(list(destinations)[0], **kw)
- if isinstance(key, xs.structures.Wildcard):
- template_map['kw_key'] = 'term'
- template_map['kw_val'] = binding_module.literal(key, **kw)
- elif isinstance(key, nfa.AllWalker):
- (mga_tag, mga_defns) = GenerateModelGroupAll(ctd, key, binding_module, template_map.copy(), **kw)
- template_map['kw_key'] = 'term'
- template_map['kw_val'] = mga_tag
- lines.extend(mga_defns)
- else:
- assert isinstance(key, xs.structures.ElementDeclaration)
- template_map['kw_key'] = 'element_use'
- template_map['kw_val'] = templates.replaceInText('%{ctd}._UseForTag(%{field_tag})', field_tag=binding_module.literal(key.expandedName(), **kw), **template_map)
- lines3.append(templates.replaceInText('%{content}.ContentModelTransition(next_state=%{next_state}, %{kw_key}=%{kw_val}),',
- **template_map))
- lines2.extend([ ' '+_l for _l in lines3 ])
- lines2.append("])")
-
- lines.append(templates.replaceInText('%{ctd}.%{cm_tag} = %{content}.ContentModel(state_map = {', **template_map))
- lines.extend([' '+_l for _l in lines2 ])
- lines.append("})")
- return (cmi, lines)
+ template_map['min_occurs'] = repr(particle.minOccurs())
+ template_map['max_occurs'] = repr(particle.maxOccurs())
+ (term_val, lines) = GenerateContentTerm(ctd, particle.term(), binding_module, **kw)
+ particle_val = templates.replaceInText('pyxb.binding.content.ParticleModel(%{term_val}, min_occurs=%{min_occurs}, max_occurs=%{max_occurs})', term_val=term_val, **template_map)
+ return (particle_val, lines)
def _useEnumerationTags (td):
assert isinstance(td, xs.structures.SimpleTypeDefinition)
@@ -692,10 +663,12 @@ class %{ctd} (%{superclass}):
%{ctd}._AddElement(pyxb.binding.basis.element(%{name_expr}, %{typeDefinition}%{element_aux_init}))
''', name_expr=binding_module.literal(ed.expandedName(), **kw), ctd=template_map['ctd'], **ef_map))
- fa = nfa.Thompson(content_basis).nfa()
- fa = fa.buildDFA()
- (cmi, cmi_defn) = GenerateContentModel(ctd=ctd, automaton=fa, binding_module=binding_module, **kw)
- outf.postscript().append("\n".join(cmi_defn))
+ cm_tag = utility.PrepareIdentifier('ContentModel', binding_module.uniqueInClass(ctd), protected=True)
+ (particle_val, lines) = GenerateContentParticle(ctd=ctd, particle=content_basis, binding_module=binding_module, **kw)
+ if lines:
+ outf.postscript().append("\n".join(lines))
+ outf.postscript().append("\n")
+ outf.postscript().append(templates.replaceInText('%{ctd}.%{cm_tag} = %{particle_val}', ctd=template_map['ctd'], cm_tag=cm_tag, particle_val=particle_val))
outf.postscript().append("\n")
if need_content:
@@ -717,43 +690,45 @@ class %{ctd} (%{superclass}):
ad = au.attributeDeclaration()
assert isinstance(ad.scope(), xs.structures.ComplexTypeDefinition), 'unexpected scope %s' % (ad.scope(),)
au_map = ad._templateMap()
- if ad.scope() == ctd:
- assert isinstance(au_map, dict)
- if au.restrictionOf() is not None:
- #print 'Local %s restriction of %s' % (au_map, au.restrictionOf().attributeDeclaration()._templateMap())
- au_map = au.restrictionOf().attributeDeclaration()._templateMap().copy()
- definitions.append(templates.replaceInText('''
- # Attribute %{id} is restricted from parent''', **au_map))
-
- assert ad.typeDefinition() is not None
- au_map['attr_type'] = binding_module.literal(ad.typeDefinition(), **kw)
-
- vc_source = ad
- if au.valueConstraint() is not None:
- vc_source = au
- aux_init = []
- if vc_source.fixed() is not None:
- aux_init.append('fixed=True')
- aux_init.append('unicode_default=%s' % (binding_module.literal(vc_source.fixed(), **kw),))
- elif vc_source.default() is not None:
- aux_init.append('unicode_default=%s' % (binding_module.literal(vc_source.default(), **kw),))
- if au.required():
- aux_init.append('required=True')
- if au.prohibited():
- aux_init.append('prohibited=True')
- if 0 == len(aux_init):
- au_map['aux_init'] = ''
- else:
- aux_init.insert(0, '')
- au_map['aux_init'] = ', '.join(aux_init)
- if ad.annotation() is not None:
- au_map['documentation'] = binding_module.literal(unicode(ad.annotation()))
- else:
- au_map['documentation'] = binding_module.literal(None)
if ad.scope() != ctd:
definitions.append(templates.replaceInText('''
# Attribute %{id} inherited from %{decl_type_en}''', decl_type_en=unicode(ad.scope().expandedName()), **au_map))
continue
+ assert isinstance(au_map, dict)
+ aur = au;
+ while aur.restrictionOf() is not None:
+ aur = aur.restrictionOf()
+ if au != aur:
+ #print 'Local %s restriction of %s' % (au_map, aur.attributeDeclaration()._templateMap())
+ au_map = aur.attributeDeclaration()._templateMap().copy()
+ definitions.append(templates.replaceInText('''
+ # Attribute %{id} is restricted from parent''', **au_map))
+
+ assert ad.typeDefinition() is not None
+ au_map['attr_type'] = binding_module.literal(ad.typeDefinition(), **kw)
+
+ vc_source = ad
+ if au.valueConstraint() is not None:
+ vc_source = au
+ aux_init = []
+ if vc_source.fixed() is not None:
+ aux_init.append('fixed=True')
+ aux_init.append('unicode_default=%s' % (binding_module.literal(vc_source.fixed(), **kw),))
+ elif vc_source.default() is not None:
+ aux_init.append('unicode_default=%s' % (binding_module.literal(vc_source.default(), **kw),))
+ if au.required():
+ aux_init.append('required=True')
+ if au.prohibited():
+ aux_init.append('prohibited=True')
+ if 0 == len(aux_init):
+ au_map['aux_init'] = ''
+ else:
+ aux_init.insert(0, '')
+ au_map['aux_init'] = ', '.join(aux_init)
+ if ad.annotation() is not None:
+ au_map['documentation'] = binding_module.literal(unicode(ad.annotation()))
+ else:
+ au_map['documentation'] = binding_module.literal(None)
attribute_uses.append(templates.replaceInText('%{use}.name() : %{use}', **au_map))
if ad.expandedName().localName() != au_map['id']:
@@ -1098,11 +1073,9 @@ class _ModuleNaming_mixin (object):
rv = component.bestNCName()
if rv is None:
if isinstance(component, xs.structures.ComplexTypeDefinition):
- rv = '_CTD_ANON_%d' % (self.__anonCTDIndex,)
- self.__anonCTDIndex += 1
+ rv = utility.PrepareIdentifier('CTD_ANON', self.uniqueInClass(component), protected=True)
elif isinstance(component, xs.structures.SimpleTypeDefinition):
- rv = '_STD_ANON_%d' % (self.__anonSTDIndex,)
- self.__anonSTDIndex += 1
+ rv = utility.PrepareIdentifier('STD_ANON', self.uniqueInClass(component), protected=True)
else:
assert False
kw['protected'] = True
@@ -1567,6 +1540,46 @@ class Generator (object):
return self
__schemaStrippedPrefix = None
+ def locationPrefixRewriteMap (self):
+ """Optional map to rewrite schema locations.
+
+ This applies only to the values of schemaLocation attributes
+ in C{import} and C{include} elements. Its purpose is to
+ convert remote or absolute schema locations into local or
+ relative ones to allow offline processing when all schema are
+ available in a local directory. See C{schemaRoot}.
+ """
+ return self.__locationPrefixRewriteMap
+ def setLocationPrefixRewriteMap (self, location_prefix_rewrite_map):
+ self.__locationPrefixMap.clear()
+ print 'GOT "%s"' % (location_prefix_rewrite_map,)
+ self.__locationPrefixMap.update(location_prefix_rewrite_map)
+ return self
+ def addLocationPrefixRewrite (self, prefix, substituent):
+ """Add a rewrite entry for schema locations.
+
+ @param prefix : A text prefix that should be removed from
+ schema location URIs.
+
+ @param substituent : The text prefix that should replace
+ C{prefix} as a prefix in a schema location URI.
+ """
+
+ self.__locationPrefixRewriteMap[prefix] = substituent
+ return self
+ def argAddLocationPrefixRewrite (self, prefix_rewrite):
+ """Add a rewrite entry for schema locations.
+
+ Parameter values are strings of the form C{pfx=sub}. The
+ effect is that a schema location that begins with C{pfx} is
+ rewritten so that it instead begins with C{sub}."""
+ try:
+ (prefix, substituent) = prefix_rewrite.split('=', 1)
+ except:
+ raise
+ self.addLocationPrefixRewrite(prefix, substituent)
+ __locationPrefixMap = {}
+
def schemaLocationList (self):
"""A list of locations from which entrypoint schemas are to be
read.
@@ -1886,6 +1899,7 @@ class Generator (object):
@keyword binding_root: Invokes L{setBindingRoot}
@keyword schema_root: Invokes L{setSchemaRoot}
@keyword schema_stripped_prefix: Invokes L{setSchemaStrippedPrefix}
+ @keyword location_prefix_rewrite_map: Invokes L{setLocationPrefixRewriteMap}
@keyword schema_location_list: Invokes L{setSchemaLocationList}
@keyword module_list: Invokes L{_setModuleList}
@keyword module_prefix: Invokes L{setModulePrefix}
@@ -1913,6 +1927,7 @@ class Generator (object):
self.__bindingRoot = kw.get('binding_root', self._DEFAULT_bindingRoot)
self.__schemaRoot = kw.get('schema_root', '.')
self.__schemaStrippedPrefix = kw.get('schema_stripped_prefix')
+ self.__locationPrefixRewriteMap = kw.get('location_prefix_rewrite_map', {})
self.__schemas = []
self.__schemaLocationList = kw.get('schema_location_list', [])[:]
self.__moduleList = kw.get('module_list', [])[:]
@@ -1951,6 +1966,7 @@ class Generator (object):
('binding_root', setBindingRoot),
('schema_root', setSchemaRoot),
('schema_stripped_prefix', setSchemaStrippedPrefix),
+ ('location_prefix_rewrite', argAddLocationPrefixRewrite),
('schema_location', setSchemaLocationList),
('module', _setModuleList),
('module_prefix', setModulePrefix),
@@ -1976,6 +1992,7 @@ class Generator (object):
self._setNamespaceVisibilities(public_namespaces, private_namespaces)
if args is not None:
self.__schemaLocationList.extend(args)
+ pyxb.utils.utility.SetLocationPrefixRewriteMap(self.locationPrefixRewriteMap())
def setFromCommandLine (self, argv=None):
if argv is None:
@@ -2009,6 +2026,8 @@ class Generator (object):
help=self.__stripSpaces(self.schemaRoot.__doc__))
group.add_option('--schema-stripped-prefix', metavar="TEXT", type='string',
help=self.__stripSpaces(self.schemaStrippedPrefix.__doc__))
+ group.add_option('--location-prefix-rewrite', metavar="TEXT", type='string',
+ help=self.__stripSpaces(self.argAddLocationPrefixRewrite.__doc__))
group.add_option('--uri-content-archive-directory', metavar="DIRECTORY",
help=self.__stripSpaces(self.uriContentArchiveDirectory.__doc__))
parser.add_option_group(group)
@@ -2110,6 +2129,8 @@ class Generator (object):
opts.append('--schema-root=' + self.schemaRoot())
if self.schemaStrippedPrefix() is not None:
opts.append('--schema-stripped-prefix=%s' + self.schemaStrippedPrefix())
+ for (pfx, sub) in self.locationPrefixRewriteMap():
+ opts.append('--location-prefix-rewrite=%s=%s' % (pfx, sub))
if self.modulePrefix() is not None:
opts.append('--module-prefix=' + self.modulePrefix())
opts.append('--binding-root=' + self.bindingRoot())
@@ -2182,7 +2203,9 @@ class Generator (object):
converter = None
try:
if converter is None:
- schema = xs.schema.CreateFromLocation(absolute_schema_location=self.normalizeSchemaLocation(sl), generation_uid=self.generationUID(), uri_content_archive_directory=self.uriContentArchiveDirectory())
+ schema = xs.schema.CreateFromLocation(absolute_schema_location=self.normalizeSchemaLocation(sl),
+ generation_uid=self.generationUID(),
+ uri_content_archive_directory=self.uriContentArchiveDirectory())
else:
schema = converter(self, sl)
self.addSchema(schema)
diff --git a/pyxb/binding/nfa.py b/pyxb/binding/nfa.py
deleted file mode 100644
index b707f4a..0000000
--- a/pyxb/binding/nfa.py
+++ /dev/null
@@ -1,392 +0,0 @@
-# Copyright 2009, Peter A. Bigot
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain a
-# copy of the License at:
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-"""Finite automata classes that support content model management."""
-
-from pyxb.xmlschema.structures import Particle, ModelGroup
-
-# Represent state transitions as a map from states to maps from
-# symbols to sets of states. States are integers.
-
-class FiniteAutomaton (dict):
- """Represent a finite automaton.
-
- The FiniteAutomaton instance is a map from states to sets of transitions.
-
- A transition is a map from a key to a set of states.
-
- States are integers. The start and end state are distinguished.
- Transitions are by value, and are one of ElementDeclaration,
- ModelGroup[all], and Wildcard. The value None represents an
- epsilon transition.
-
- """
-
- # A unique identifier used for creating states
- __stateID = -1
-
- # The state serving as the automaton start.
- __start = None
-
- # The state serving as the automaton end.
- __end = None
-
- def __init__ (self):
- self.__end = self.newState()
- self.__start = self.newState()
-
- def newState (self):
- """Create a new node in the automaton. No transitions are added."""
- self.__stateID += 1
- self.setdefault(self.__stateID, {})
- return self.__stateID
-
- def start (self):
- """Obtain the start node of the automaton."""
- return self.__start
-
- def end (self):
- """Obtain the end node of the automaton."""
- return self.__end
-
- def addTransition (self, key, source, destination):
- """Add a transition on key from the source state to the destination state."""
- assert destination is not None
- self.setdefault(source, {}).setdefault(key, set()).add(destination)
- return self
-
- def ok (self, key, source, destination):
- """Return True iff the automaton can transition from source to
- destination on key."""
- return destination in self[source].get(key, set())
-
- def addSubAutomaton (self, nfa):
- """Copy the given automaton into this one. Returns a pair of
- the start and end states of the copied sub-automaton."""
- nfa_base_id = self.__stateID+1
- self.__stateID += len(nfa)
- for sub_state in nfa.keys():
- ssid = sub_state + nfa_base_id
- ss_map = self.setdefault(ssid, {})
- for key in nfa[sub_state]:
- ss_map.setdefault(key, set())
- ss_map[key] = ss_map[key].union(set([ (nfa_base_id+_i) for _i in nfa[sub_state][key]]))
- return (nfa_base_id+nfa.start(), nfa_base_id+nfa.end())
-
- def alphabet (self):
- """Determine the keys that allow transitions in the automaton."""
- elements = set()
- for s in self.keys():
- transitions = self[s]
- elements = elements.union(transitions.keys())
- elements.discard(None)
- return elements
-
- def isFullPath (self, steps):
- """Return True iff the automaton can be traversed from start
- to end following the given steps, including arbitrary epsilon
- moves."""
- reaches = self.epsilonClosure([self.start()])
- #print 'Starting full path from %s\n%s\n' % (reaches, self)
- for s in steps:
- reaches = self.epsilonClosure(self.move(reaches, s))
- return self.end() in reaches
-
- def move (self, states, key):
- """Determine the set of states reachable from the input set of states by one key transition."""
- next_states = set()
- for s in states:
- next_states = next_states.union(self[s].get(key, set()))
- #print 'Move from %s via %s is %s' % (states, key, next_states)
- return next_states
-
- def epsilonClosure (self, states):
- """Calculate the epsilon closure of the given set of states."""
- states = set(states)
- while True:
- new_states = states.union(self.move(states, None))
- if states == new_states:
- return states
- states = new_states
-
- def reverseTransitions (self):
- reverse_map = { }
- for state in self.keys():
- transitions = self[state]
- for key in transitions.keys():
- [ reverse_map.setdefault(_s, {}).setdefault(key, set()).add(state) for _s in transitions[key] ]
- #print reverse_map
- return reverse_map
-
- def minimizeDFA (self, final_states):
- nonfinal_states = tuple(set(self.keys()).difference(set(final_states)))
- alphabet = self.alphabet()
- reverse_map = self.reverseTransitions()
- work = set([ final_states, nonfinal_states ])
- partition = work.copy()
- while 0 < len(work):
- states = set(work.pop())
- #print 'State %s, partition %s' % (states, partition)
- for key in alphabet:
- sources = set()
- [ sources.update(reverse_map.get(_s, {}).get(key, set())) for _s in states ]
- new_partition = set()
- for r in partition:
- rs = set(r)
- if (0 < len(sources.intersection(rs))) and (0 < len(rs.difference(sources))):
- r1 = tuple(rs.intersection(sources))
- r2 = tuple(rs.difference(r1))
- #print 'Split on %s: %s and %s' % (key, r1, r2)
- new_partition.add(r1)
- new_partition.add(r2)
- if r in work:
- work.remove(r)
- work.add(r1)
- work.add(r2)
- elif len(r1) <= len(r2):
- work.add(r1)
- else:
- work.add(r2)
- else:
- new_partition.add(r)
- partition = new_partition
- translate_map = { }
- min_dfa = FiniteAutomaton()
- for p in partition:
- if self.start() in p:
- new_state = min_dfa.start()
- elif self.end() in p:
- new_state = min_dfa.end()
- else:
- new_state = min_dfa.newState()
- #print 'Convert %s to %s' % (p, new_state)
- for max_state in p:
- assert max_state not in translate_map
- translate_map[max_state] = new_state
-
- for f in final_states:
- self.addTransition(None, f, self.end())
- #print 'DFA: %s' % (self,)
- for (state, transitions) in self.items():
- for (key, destination) in transitions.items():
- assert 1 == len(destination)
- d = destination.copy().pop()
- #print 'Old: %s via %s to %s\nNew: %s via %s to %s' % (state, key, d, translate_map[state], key, translate_map[d])
- min_dfa.addTransition(key, translate_map[state], translate_map[d])
-
- # Just in case self.start() and self.end() are in the same partition
- min_dfa.addTransition(None, translate_map[self.end()], min_dfa.end())
- #print 'Final added %d jump to %d' % (translate_map[self.end()], min_dfa.end())
-
- #print 'DFA: %s' % (self,)
- #print 'Start: %s' % (translate_map[self.start()],)
- #print 'Final: %s' % (set([ translate_map[_s] for _s in final_states ]).pop(),)
- #print 'Partition: %s' % (partition,)
- #print 'Minimized: %s' % (min_dfa,)
- #print "Resulting DFA:\n%s\n\n" % (min_dfa,)
- #print 'Minimal DFA has %d states, original had %d' % (len(min_dfa), len(self))
- return min_dfa
-
- def buildDFA (self):
- """Build a deterministic finite automaton that accepts the
- same language as this one.
-
- The resulting automaton has epsilon transitions only from
- terminal states to the DFA distinguished end state."""
-
- #print "Building DFA from NFA:\n%s\n" % (self,)
- dfa = FiniteAutomaton()
- ps0 = tuple(self.epsilonClosure([ dfa.start() ]))
- #print "Start state is %s" % (ps0,)
- pset_to_state = { ps0 : dfa.start() }
- changing = True
- alphabet = self.alphabet()
- while changing:
- changing = False
- for (psi, dfa_state) in pset_to_state.items():
- for key in alphabet:
- assert key is not None
- ns = tuple(self.epsilonClosure(self.move(psi, key)))
- if 0 == len(ns):
- #print 'From %s via %s is dead' % (psi, key)
- continue
- #print 'From %s via %s can reach %s' % (psi, key, ns)
- new_state = pset_to_state.get(ns, None)
- if new_state is None:
- new_state = dfa.newState()
- pset_to_state[ns] = new_state
- #print "New state %d is %s" % (new_state, ns)
- if not dfa.ok(key, dfa_state, new_state):
- changing = True
- dfa.addTransition(key, dfa_state, new_state)
- final_states = set()
- for (psi, dfa_state) in pset_to_state.items():
- if self.end() in psi:
- final_states.add(dfa_state)
- return dfa.minimizeDFA(tuple(final_states))
-
- def __str__ (self):
- states = set(self.keys())
- elements = set()
- for k in self.keys():
- transitions = self[k]
- elements = elements.union(transitions.keys())
- for step in transitions.keys():
- states = states.union(transitions[step])
- states = list(states)
- states.sort()
- strings = []
- for source in states:
- if source == self.end():
- strings.append('%s terminates' % (source,))
- continue
- transitions = self[source]
- if 0 == len(transitions):
- strings.append('%s dead-ends' % (source,))
- continue
- for step in transitions.keys():
- strings.append('%s via %s to %s' % (source, step, ' '.join([ str(_s) for _s in transitions[step]])))
- return "\n".join(strings)
-
-def _Permutations (particles):
- if 1 == len(particles):
- yield tuple(particles)
- else:
- for i in range(len(particles)):
- this = particles[i]
- rest = particles[:i] + particles[i+1:]
- for p in _Permutations(rest):
- yield (this,) + p
-
-class AllWalker (object):
- """A list of minimized DFAs each of which is a single option within
- an ALL model group."""
- __particles = None
-
- def __init__ (self):
- self.__particles = [ ]
-
- def particles (self): return self.__particles
-
- def add (self, dfa, is_required):
- self.__particles.append( ( dfa, is_required ) )
-
-class Thompson:
- """Create a NFA from a content model. Reminiscent of Thompson's
- algorithm for creating an NFA from a regular expression. See
- U{http://portal.acm.org/citation.cfm?doid=363387}"""
-
- # The NFA
- __nfa = None
-
- def nfa (self):
- return self.__nfa
-
- def __init__ (self, term=None):
- self.__nfa = FiniteAutomaton()
- if term is not None:
- #assert isinstance(term, Particle)
- self.addTransition(term, self.__nfa.start(), self.__nfa.end())
-
- def addTransition (self, term, start, end):
- """Interpret the term and update the NFA to support a path
- from start to end that is consistent with the term.
-
- Particles express looping operations with minimum and maximum
- iteration counts.
-
- Model groups express control structures: ordered and unordered
- sequences, and alternatives.
-
- Anything else is assumed to be a character in the automaton
- alphabet.
- """
- if isinstance(term, Particle):
- return self.__fromParticle(term, start, end)
- if isinstance(term, ModelGroup):
- return self.__fromModelGroup(term, start, end)
- self.__nfa.addTransition(term, start, end)
-
- def __fromParticle (self, particle, start, end):
- """Add transitions to interpret the particle."""
-
- #print '# %d to %s of %s' % (particle.minOccurs(), particle.maxOccurs(), particle.term())
-
- # If possible, epsilon transition straight from start to end.
- if 0 == particle.minOccurs():
- self.addTransition(None, start, end)
-
- # Add term transitions from start through the minimum number
- # of instances of the term.
- cur_start = next_end = start
- for step in range(0, particle.minOccurs()):
- cur_start = next_end
- next_end = self.__nfa.newState()
- self.addTransition(particle.term(), cur_start, next_end)
-
- if None is particle.maxOccurs():
- # Add a back loop to repeat the last instance of the term
- # (creating said instance, if we haven't already)
- if next_end == start:
- self.addTransition(particle.term(), start, end)
- next_end = end
- self.addTransition(None, next_end, cur_start)
- else:
- # Add additional terms up to the maximum, with a short-cut
- # exit for those above the minOccurs value.
- for step in range(particle.minOccurs(), particle.maxOccurs()):
- cur_start = next_end
- next_end = self.__nfa.newState()
- self.addTransition(None, cur_start, end)
- self.addTransition(particle.term(), cur_start, next_end)
- # Leave the sub-FA
- self.addTransition(None, next_end, end)
-
- def __fromMGSequence (self, particles, start, end):
- # Just step from one to the next
- #print '# Sequence of %s' % (particles,)
- for p in particles:
- next_state = self.__nfa.newState()
- self.addTransition(p, start, next_state)
- start = next_state
- self.addTransition(None, start, end)
-
- def __fromMGChoice (self, particles, start, end):
- # Start to end for each choice, but make the choice stage
- # occur once (in case a particle adds a transition from end to
- # start.
- for p in particles:
- choice_start = self.__nfa.newState()
- choice_end = self.__nfa.newState()
- self.addTransition(None, start, choice_start)
- self.addTransition(p, choice_start, choice_end)
- self.addTransition(None, choice_end, end)
-
- def __fromMGAll (self, particles, start, end):
- # All is too ugly: exponential state growth. Use a special
- # construct instead.
- walker = AllWalker()
- for p in particles:
- walker.add(Thompson(p).nfa().buildDFA(), 0 < p.minOccurs())
- self.addTransition(walker, start, end)
-
- def __fromModelGroup (self, group, start, end):
- # Do the right thing based on the model group compositor
- if ModelGroup.C_ALL == group.compositor():
- return self.__fromMGAll(group.particles(), start, end)
- if ModelGroup.C_CHOICE == group.compositor():
- return self.__fromMGChoice(group.particles(), start, end)
- if ModelGroup.C_SEQUENCE == group.compositor():
- return self.__fromMGSequence(group.particles(), start, end)
- assert False
diff --git a/pyxb/bundles/common/scripts/genbind b/pyxb/bundles/common/scripts/genbind
index 4a44b57..a0842a5 100755
--- a/pyxb/bundles/common/scripts/genbind
+++ b/pyxb/bundles/common/scripts/genbind
@@ -9,9 +9,11 @@ BUNDLE_TAG=common
test -f ${SCHEMA_DIR}/xml.xsd || wget -O ${SCHEMA_DIR}/xml.xsd http://www.w3.org/2001/xml.xsd
test -f ${SCHEMA_DIR}/XMLSchema.xsd || wget -O ${SCHEMA_DIR}/XMLSchema.xsd http://www.w3.org/2001/XMLSchema.xsd
+# XHTML "schema" no longer available
+# http://www.w3.org/1999/xhtml.xsd xhtml --allow-builtin-generation
+
( cat <<EOList
http://www.w3.org/2001/XMLSchema-hasFacetAndProperty xsd_hfp --allow-builtin-generation
-http://www.w3.org/1999/xhtml.xsd xhtml --allow-builtin-generation
EOList
) | generateBindings
diff --git a/pyxb/bundles/opengis/scripts/genbind b/pyxb/bundles/opengis/scripts/genbind
index c74caac..3e92507 100755
--- a/pyxb/bundles/opengis/scripts/genbind
+++ b/pyxb/bundles/opengis/scripts/genbind
@@ -29,6 +29,7 @@ EOText
) | generateBindings
pyxbgen \
+ --location-prefix-rewrite=http://schemas.opengis.net/=${SCHEMA_DIR}/ \
--schema-location=${SCHEMA_DIR}/gml/3.2.1/gml.xsd --module=gml_3_2 \
--schema-location=${SCHEMA_DIR}/iso/19139/20070417/gmd/gmd.xsd --module=iso19139.gmd \
--schema-location=${SCHEMA_DIR}/iso/19139/20070417/gts/gts.xsd --module=iso19139.gts \
@@ -40,8 +41,6 @@ pyxbgen \
--archive-to-file=${ARCHIVE_DIR}/iso19139.core.wxs \
|| failure gml_3_2
-printenv | sort
-
( cat <<EOText
${SCHEMA_DIR}/iso/19139/20070417/gmx/gmx.xsd gmx
${SCHEMA_DIR}/gml/3.1.1/base/gml.xsd gml
@@ -74,5 +73,6 @@ ${SCHEMA_DIR}/citygml/transportation/1.0/transportation.xsd citygml.transportati
${SCHEMA_DIR}/citygml/vegetation/1.0/vegetation.xsd citygml.vegetation
${SCHEMA_DIR}/citygml/waterbody/1.0/waterBody.xsd citygml.waterBody
EOText
-) | generateBindings
+) | generateBindings \
+ --location-prefix-rewrite=http://schemas.opengis.net/=${SCHEMA_DIR}/
diff --git a/pyxb/exceptions_.py b/pyxb/exceptions_.py
index c4653f6..e2272b6 100644
--- a/pyxb/exceptions_.py
+++ b/pyxb/exceptions_.py
@@ -189,6 +189,14 @@ class UnrecognizedElementError (UnrecognizedContentError):
kw.setdefault('message', 'No element binding available for %s' % (self.__elementName,))
UnrecognizedContentError.__init__(self, self.__domNode, **kw)
+class MissingElementError (StructuralBadDocumentError):
+ """Content requires an element that is not present."""
+ pass
+
+class UnexpectedElementError (StructuralBadDocumentError):
+ """More instances of an element are present than permitted by the content model."""
+ pass
+
class ExtraContentError (StructuralBadDocumentError):
"""Raised when processing document and there is more material in an element content than expected."""
diff --git a/pyxb/utils/utility.py b/pyxb/utils/utility.py
index 53c6ca9..1fd0354 100644
--- a/pyxb/utils/utility.py
+++ b/pyxb/utils/utility.py
@@ -486,12 +486,38 @@ class Graph:
raise Exception('DFS walk did not cover all nodes (walk %d versus nodes %d)' % (len(self.__dfsOrder), len(self.__nodes)))
return self.__dfsOrder
-def NormalizeLocation (uri, parent_uri=None):
+LocationPrefixRewriteMap_ = { }
+
+def SetLocationPrefixRewriteMap (prefix_map):
+ """Set the map that is used to by L{NormalizeLocation} to rewrite URI prefixes."""
+
+ LocationPrefixRewriteMap_.clear()
+ LocationPrefixRewriteMap_.update(prefix_map)
+
+def NormalizeLocation (uri, parent_uri=None, prefix_map=None):
"""Normalize a URI against an optional parent_uri in the way that is
done for C{schemaLocation} attribute values.
If no URI schema is present, this will normalize a file system
- path."""
+ path.
+
+ Optionally, the resulting absolute URI can subsequently be
+ rewritten to replace specified prefix strings with alternative
+ strings, e.g. to convert a remote URI to a local repository. This
+ rewriting is done after the conversion to an absolute URI, but
+ before normalizing file system URIs.
+
+ @param uri : The URI to normalize. If C{None}, function returns
+ C{None}
+
+ @param parent_uri : The base URI against which normalization is
+ done, if C{uri} is a relative URI.
+
+ @param prefix_map : A map used to rewrite URI prefixes. If
+ C{None}, the value defaults to that stored by
+ L{SetLocationPrefixRewriteMap}.
+
+ """
import urlparse
import os
@@ -503,6 +529,11 @@ def NormalizeLocation (uri, parent_uri=None):
#if (0 > parent_uri.find(':')) and (not parent_uri.endswith(os.sep)):
# parent_uri = parent_uri + os.sep
abs_uri = urlparse.urljoin(parent_uri, uri)
+ if prefix_map is None:
+ prefix_map = LocationPrefixRewriteMap_
+ for (pfx, sub) in prefix_map.items():
+ if abs_uri.startswith(pfx):
+ abs_uri = sub + abs_uri[len(pfx):]
if 0 > abs_uri.find(':'):
abs_uri = os.path.realpath(abs_uri)
return abs_uri
diff --git a/pyxb/xmlschema/structures.py b/pyxb/xmlschema/structures.py
index 6bca3a8..8506686 100644
--- a/pyxb/xmlschema/structures.py
+++ b/pyxb/xmlschema/structures.py
@@ -161,10 +161,12 @@ class _SchemaComponent_mixin (pyxb.namespace._ComponentDependency_mixin,
self.__namespaceContext = pyxb.namespace.resolution.NamespaceContext.GetNodeContext(node)
if self.__namespaceContext is None:
raise pyxb.LogicError('No namespace_context for schema component')
+
+ super(_SchemaComponent_mixin, self).__init__(*args, **kw)
+
if isinstance(node, pyxb.utils.utility.Locatable_mixin):
- self._setLocation(node.location)
+ self._setLocation(node._location())
- super(_SchemaComponent_mixin, self).__init__(*args, **kw)
self._namespaceContext().targetNamespace()._associateComponent(self)
self._setOwner(kw.get('owner'))
@@ -4820,7 +4822,7 @@ class Schema (_SchemaComponent_mixin):
@keyword absolute_schema_location: A file path or URI. This value is
not normalized, and supersedes C{schema_location}.
"""
- schema_location = kw.pop('absolute_schema_location', pyxb.utils.utility.NormalizeLocation(kw.get('schema_location'), kw.get('parent_uri')))
+ schema_location = kw.pop('absolute_schema_location', pyxb.utils.utility.NormalizeLocation(kw.get('schema_location'), kw.get('parent_uri'), kw.get('prefix_map')))
kw['location_base'] = kw['schema_location'] = schema_location
assert isinstance(schema_location, basestring), 'Unexpected value %s type %s for schema_location' % (schema_location, type(schema_location))
uri_content_archive_directory = kw.get('uri_content_archive_directory')
diff --git a/setup.py b/setup.py
index 7356ce6..fed7e1f 100755
--- a/setup.py
+++ b/setup.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python
# The current version of the system. Format is #.#.#[-DEV].
-version = '1.1.1'
+version = '1.1.2'
import distutils.sysconfig
diff --git a/tests/bindings/test-constraints.py b/tests/bindings/test-constraints.py
index 22dc87d..b3f8094 100644
--- a/tests/bindings/test-constraints.py
+++ b/tests/bindings/test-constraints.py
@@ -227,5 +227,12 @@ class testWhitespace (unittest.TestCase):
self.assertEqual("test", CollapseString(u"\ttest\n\r"))
self.assertEqual("test too", CollapseString(u"\ttest\n\rtoo\n"))
+ def testApplyWhitespace (self):
+ goal = 'one two'
+ source = ' one two '
+ self.assertEqual(goal, CollapseString(goal))
+ self.assertEqual(goal, CollapseString(source))
+ self.assertEqual(source, CollapseString(source, _apply_whitespace_facet=False))
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/bindings/test-nfa.py b/tests/bindings/test-nfa.py
deleted file mode 100644
index 983b70c..0000000
--- a/tests/bindings/test-nfa.py
+++ /dev/null
@@ -1,199 +0,0 @@
-import unittest
-from pyxb.utils.utility import *
-from pyxb.utils.utility import _DeconflictSymbols_mixin
-
-'''
-
-class TestThompson (unittest.TestCase):
-
- def testParticleOne (self):
- t = Thompson(Particle(1,1,'a'))
- for nfa in (t.nfa(), t.nfa().buildDFA()):
- self.assertFalse(nfa.isFullPath([]))
- self.assertTrue(nfa.isFullPath(['a']))
- self.assertFalse(nfa.isFullPath(['a', 'a']))
-
- def testParticleOptional (self):
- t = Thompson(Particle(0,1,'a'))
- for nfa in (t.nfa(), t.nfa().buildDFA()):
- self.assertTrue(nfa.isFullPath([]))
- self.assertTrue(nfa.isFullPath(['a']))
- self.assertFalse(nfa.isFullPath(['a', 'a']))
-
- def testParticleAny (self):
- t = Thompson(Particle(0,None,'a'))
- for nfa in (t.nfa(), t.nfa().buildDFA()):
- self.assertTrue(nfa.isFullPath([]))
- self.assertTrue(nfa.isFullPath(['a']))
- for rep in range(0, 10):
- self.assertTrue(nfa.isFullPath(rep * ['a']))
-
- def testParticle2Plus (self):
- particle = Particle(2, None, 'a')
- t = Thompson(particle)
- for nfa in (t.nfa(), t.nfa().buildDFA()):
- for rep in range(1, 10):
- if particle.minOccurs() <= rep:
- self.assertTrue(nfa.isFullPath(rep * ['a']))
- else:
- self.assertFalse(nfa.isFullPath(rep * ['a']))
-
- def testParticleSome (self):
- particle = Particle(3, 5, 'a')
- t = Thompson(particle)
- for nfa in (t.nfa(), t.nfa().buildDFA()):
- for rep in range(1, 10):
- if (particle.minOccurs() <= rep) and (rep <= particle.maxOccurs()):
- self.assertTrue(nfa.isFullPath(rep * ['a']))
- else:
- self.assertFalse(nfa.isFullPath(rep * ['a']))
-
- def testSequence1 (self):
- seq = ModelGroup(ModelGroup.C_SEQUENCE, [ 'a' ])
- t = Thompson(seq)
- for nfa in (t.nfa(), t.nfa().buildDFA()):
- self.assertFalse(nfa.isFullPath([ ]))
- self.assertTrue(nfa.isFullPath([ 'a' ]))
- self.assertFalse(nfa.isFullPath([ 'a', 'b' ]))
-
- def testSequence3 (self):
- seq = ModelGroup(ModelGroup.C_SEQUENCE, [ 'a', 'b', 'c' ])
- t = Thompson(seq)
- for nfa in (t.nfa(), t.nfa().buildDFA()):
- self.assertFalse(nfa.isFullPath([ ]))
- self.assertFalse(nfa.isFullPath([ 'a' ]))
- self.assertFalse(nfa.isFullPath([ 'a', 'b' ]))
- self.assertTrue(nfa.isFullPath([ 'a', 'b', 'c' ]))
- self.assertFalse(nfa.isFullPath([ 'a', 'b', 'c', 'd' ]))
-
- def testChoice1 (self):
- seq = ModelGroup(ModelGroup.C_CHOICE, [ 'a' ])
- t = Thompson(seq)
- for nfa in (t.nfa(), t.nfa().buildDFA()):
- self.assertFalse(nfa.isFullPath([ ]))
- self.assertTrue(nfa.isFullPath([ 'a' ]))
- self.assertFalse(nfa.isFullPath([ 'a', 'b' ]))
-
- def testChoice3 (self):
- seq = ModelGroup(ModelGroup.C_CHOICE, [ 'a', 'b', 'c' ])
- t = Thompson(seq)
- for nfa in (t.nfa(), t.nfa().buildDFA()):
- self.assertFalse(nfa.isFullPath([ ]))
- self.assertTrue(nfa.isFullPath([ 'a' ]))
- self.assertTrue(nfa.isFullPath([ 'b' ]))
- self.assertTrue(nfa.isFullPath([ 'c' ]))
- self.assertFalse(nfa.isFullPath([ 'a', 'b' ]))
-
- def testAll1 (self):
- seq = ModelGroup(ModelGroup.C_ALL, [ 'a' ])
- t = Thompson(seq)
- for nfa in (t.nfa(), t.nfa().buildDFA()):
- self.assertFalse(nfa.isFullPath([ ]))
- self.assertTrue(nfa.isFullPath([ 'a' ]))
- self.assertFalse(nfa.isFullPath([ 'a', 'a' ]))
-
- def testAll2 (self):
- seq = ModelGroup(ModelGroup.C_ALL, [ 'a', 'b' ])
- t = Thompson(seq)
- for nfa in (t.nfa(), t.nfa().buildDFA()):
- self.assertFalse(nfa.isFullPath([ ]))
- self.assertFalse(nfa.isFullPath([ 'a' ]))
- self.assertFalse(nfa.isFullPath([ 'a', 'a' ]))
- self.assertTrue(nfa.isFullPath([ 'a', 'b' ]))
- self.assertTrue(nfa.isFullPath([ 'b', 'a' ]))
- self.assertFalse(nfa.isFullPath([ 'a', 'b', 'a' ]))
- self.assertFalse(nfa.isFullPath([ 'b', 'a', 'b' ]))
-
- def testAll3 (self):
- seq = ModelGroup(ModelGroup.C_ALL, [ 'a', 'b', 'c' ])
- t = Thompson(seq)
- for nfa in (t.nfa(), t.nfa().buildDFA()):
- self.assertFalse(nfa.isFullPath([ ]))
- self.assertFalse(nfa.isFullPath([ 'a' ]))
- self.assertFalse(nfa.isFullPath([ 'a', 'a' ]))
- self.assertFalse(nfa.isFullPath([ 'a', 'b' ]))
- self.assertFalse(nfa.isFullPath([ 'b', 'a' ]))
- self.assertTrue(nfa.isFullPath([ 'a', 'b', 'c' ]))
- self.assertTrue(nfa.isFullPath([ 'a', 'c', 'b' ]))
- self.assertTrue(nfa.isFullPath([ 'b', 'a', 'c' ]))
- self.assertTrue(nfa.isFullPath([ 'b', 'c', 'a' ]))
- self.assertTrue(nfa.isFullPath([ 'c', 'a', 'b' ]))
- self.assertTrue(nfa.isFullPath([ 'c', 'b', 'a' ]))
-
-class TestFiniteAutomaton (unittest.TestCase):
- def testSubAutomaton (self):
- subnfa = FiniteAutomaton()
- subnfa.addTransition('a', subnfa.start(), subnfa.end())
- nfa = FiniteAutomaton()
- ( start, end ) = nfa.addSubAutomaton(subnfa)
- nfa.addTransition('b', nfa.start(), start)
- nfa.addTransition('c', end, nfa.end())
- self.assertFalse(nfa.isFullPath([ ]))
- self.assertTrue(nfa.isFullPath(['b', 'a', 'c']))
-
- def testSubAutomaton (self):
- subnfa = FiniteAutomaton()
- subnfa.addTransition('a', subnfa.start(), subnfa.end())
- nfa = FiniteAutomaton()
- ( start, end ) = nfa.addSubAutomaton(subnfa)
- nfa.addTransition('b', nfa.start(), start)
- nfa.addTransition(None, end, nfa.end())
- ( start, end ) = nfa.addSubAutomaton(subnfa)
- nfa.addTransition(None, nfa.start(), start)
- nfa.addTransition('b', end, nfa.end())
-
- self.assertFalse(nfa.isFullPath([ ]))
- self.assertTrue(nfa.isFullPath(['b', 'a']))
- self.assertTrue(nfa.isFullPath(['a', 'b']))
- self.assertFalse(nfa.isFullPath(['a', 'a']))
- self.assertFalse(nfa.isFullPath(['b', 'a', 'b']))
- self.assertFalse(nfa.isFullPath(['a', 'b', 'a']))
-
- def testDFA (self):
- nfa = FiniteAutomaton()
- q1 = nfa.newState()
- nfa.addTransition(None, nfa.start(), q1)
- nfa.addTransition('a', q1, q1)
- nfa.addTransition('b', q1, q1)
- q2 = nfa.newState()
- nfa.addTransition('a', q1, q2)
- q3 = nfa.newState()
- nfa.addTransition('b', q2, q3)
- nfa.addTransition('b', q3, nfa.end())
- dfa = nfa.buildDFA()
-
-class TestPermutations (unittest.TestCase):
- def testPermutations (self):
- p1 = set(_Permutations(['a']))
- self.assertEqual(1, len(p1))
-
- p2 = set(_Permutations(['a', 'b']))
- self.assertEqual(2, len(p2))
- self.assertTrue(('a', 'b') in p2)
- self.assertTrue(('b', 'a') in p2)
-
- p3 = set(_Permutations(['a', 'b', 'c']))
- self.assertEqual(6, len(p3))
- self.assertTrue(('a', 'b', 'c') in p3)
- self.assertTrue(('a', 'c', 'b') in p3)
- self.assertTrue(('b', 'a', 'c') in p3)
- self.assertTrue(('b', 'c', 'a') in p3)
- self.assertTrue(('c', 'a', 'b') in p3)
- self.assertTrue(('c', 'b', 'a') in p3)
-
-class TestSchema (unittest.TestCase):
- def testWsdl (self):
- x = ModelGroup(ModelGroup.C_CHOICE, [ 'a', 'b', 'c' ])
- x = ModelGroup(ModelGroup.C_SEQUENCE, [ Particle(0, None, x) ])
- x = ModelGroup(ModelGroup.C_SEQUENCE, [ Particle(0, None, 'W'), x ])
- x = ModelGroup(ModelGroup.C_SEQUENCE, [ Particle(0, 1, 'd'), x ])
- t = Thompson(x)
- for nfa in ( t.nfa(), t.nfa().buildDFA() ):
- self.assertTrue(nfa.isFullPath([ 'd' ]))
- self.assertFalse(nfa.isFullPath([ 'd', 'd' ]))
-
-if __name__ == '__main__':
- unittest.main()
-
-'''
-
diff --git a/tests/drivers/test-ctd-extension.py b/tests/drivers/test-ctd-extension.py
index fd75642..590f8bb 100644
--- a/tests/drivers/test-ctd-extension.py
+++ b/tests/drivers/test-ctd-extension.py
@@ -56,7 +56,7 @@ class TestCTDExtension (unittest.TestCase):
self.assertEqual('add generation', instance.eAttr)
def testMidWildcard (self):
- xml = '<defs><documentation/><something/><message/><message/><import/><message/></defs>'
+ xml = '<defs xmlns:other="other"><documentation/><other:something/><message/><message/><import/><message/></defs>'
doc = pyxb.utils.domutils.StringToDOM(xml)
instance = defs.createFromDOM(doc.documentElement)
self.assertFalse(instance.documentation is None)
@@ -64,7 +64,7 @@ class TestCTDExtension (unittest.TestCase):
self.assertEqual(1, len(instance.import_))
self.assertEqual(1, len(instance.wildcardElements()))
- xml = '<defs><something/><else/><message/><message/><import/><message/></defs>'
+ xml = '<defs xmlns:other="other"><other:something/><other:else/><message/><message/><import/><message/></defs>'
doc = pyxb.utils.domutils.StringToDOM(xml)
instance = defs.createFromDOM(doc.documentElement)
self.assertTrue(instance.documentation is None)
@@ -73,7 +73,7 @@ class TestCTDExtension (unittest.TestCase):
self.assertEqual(2, len(instance.wildcardElements()))
def testEndWildcard (self):
- xml = '<defs><message/><something/></defs>'
+ xml = '<defs xmlns:other="other"><message/><other:something/></defs>'
doc = pyxb.utils.domutils.StringToDOM(xml)
self.assertRaises(ExtraContentError, defs.createFromDOM, doc.documentElement)
diff --git a/tests/trac/test-trac-0033a.py b/tests/trac/test-trac-0033a.py
new file mode 100644
index 0000000..66f3902
--- /dev/null
+++ b/tests/trac/test-trac-0033a.py
@@ -0,0 +1,54 @@
+import pyxb.binding.generate
+import pyxb.utils.domutils
+from xml.dom import Node
+
+import os.path
+xsd='''<?xml version="1.0" encoding="UTF-8"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
+<xs:complexType name="tAddress">
+ <xs:choice>
+ <xs:sequence>
+ <xs:element name="Line1" type="xs:string"/>
+ <xs:element name="Line2" type="xs:string"/>
+ </xs:sequence>
+ <xs:sequence>
+ <xs:element name="Missing" type="xs:string"/>
+ </xs:sequence>
+ </xs:choice>
+</xs:complexType>
+<xs:complexType name="tOther">
+ <xs:sequence>
+ <xs:element name="Header" type="xs:string"/>
+ <xs:choice>
+ <xs:sequence>
+ <xs:element name="Special" type="tAddress"/>
+ <xs:element name="Common" type="tAddress" minOccurs="0"/>
+ </xs:sequence>
+ <xs:sequence>
+ <xs:element name="Common" type="tAddress"/>
+ </xs:sequence>
+ </xs:choice>
+ </xs:sequence>
+</xs:complexType>
+<xs:element name="elt" type="tOther"/>
+</xs:schema>'''
+
+code = pyxb.binding.generate.GeneratePython(schema_text=xsd)
+file('code.py', 'w').write(code)
+#print code
+
+rv = compile(code, 'test', 'exec')
+eval(rv)
+
+from pyxb.exceptions_ import *
+
+import unittest
+
+class TestTrac0033a (unittest.TestCase):
+ def test (self):
+ xml = '<elt><Header/><Common><Line1/><Line2/></Common></elt>'
+ instance = CreateFromDocument(xml)
+
+if __name__ == '__main__':
+ unittest.main()
+
diff --git a/tests/trac/trac-0033/test.sh b/tests/trac/trac-0033/test.sh
new file mode 100755
index 0000000..da464a9
--- /dev/null
+++ b/tests/trac/trac-0033/test.sh
@@ -0,0 +1,7 @@
+fail () {
+ echo 1>&2 "${test_name} FAILED: ${@}"
+ exit 1
+}
+
+python tread.py || fail trac33
+echo 'trac33 passed'
diff --git a/tests/trac/trac-0033/tread.py b/tests/trac/trac-0033/tread.py
new file mode 100644
index 0000000..08b0b8c
--- /dev/null
+++ b/tests/trac/trac-0033/tread.py
@@ -0,0 +1,51 @@
+import time
+import pyxb.binding.generate
+import pyxb.utils.domutils
+
+max_reps = 20
+
+def buildTest (num_reps, constraint='minOccurs="0" maxOccurs="1"'):
+ edefs = []
+ cdefs = []
+ duse = []
+ for r in xrange(num_reps):
+ edefs.append('<xs:element name="rep%d" type="xs:string"/>' % (r,))
+ cdefs.append('<xs:element ref="rep%d" %s/>' % (r, constraint))
+ duse.append('<rep%d>text_%d</rep%d>' % (r, r, r))
+
+ schema = ''.join([ '''<?xml version="1.0" encoding="UTF-8"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">''',
+ "\n".join(edefs),
+ '''<xs:element name="collection">
+<xs:complexType><xs:sequence>''',
+ "\n".join(cdefs),
+ '''</xs:sequence></xs:complexType>
+</xs:element>
+</xs:schema>''' ])
+
+ xmls = '<collection>' + ''.join(duse) + '</collection>'
+
+ return (schema, xmls)
+
+for size in xrange(1, max_reps):
+ (schema, xmls) = buildTest(size)
+
+ t0 = time.time()
+ code = pyxb.binding.generate.GeneratePython(schema_text=schema)
+ t1 = time.time()
+ rv = compile(code, 'test', 'exec')
+ t2 = time.time()
+ eval(rv)
+ t3 = time.time()
+ #file('code.py', 'w').write(code)
+ #print xmls
+ ct0 = time.time()
+ doc = CreateFromDocument(xmls)
+ ct1 = time.time()
+
+ print "%d gen=%g cpl=%g ld=%g prs=%g" % (size, t1 - t0, t2 - t1, t3 - t2, ct1 - ct0)
+ # Should not take more than a second (really, less than 10ms)
+ assert (ct1 - ct0) < 1.0
+ #file('code.py', 'w').write(code)
+
+
diff --git a/tests/trac/trac-0080/check.py b/tests/trac/trac-0080/check.py
new file mode 100644
index 0000000..54dfa55
--- /dev/null
+++ b/tests/trac/trac-0080/check.py
@@ -0,0 +1,65 @@
+import mr
+import unittest
+import pyxb
+import pyxb.binding.datatypes as xsd
+
+class TestTrac0080 (unittest.TestCase):
+
+ _NotANormalizedString = "\nmulti\nline\ttabbed\n"
+ _NotAToken = ' leading spaces '
+ _NotAnNCName = 'internal spaces'
+ _NCName = 'simple'
+
+ def assignAttribute_ (self, instance, value):
+ instance.anAttribute = value
+
+ def testType4 (self): # base
+ v = xsd.normalizedString(self._NotANormalizedString)
+ i4 = mr.Type4()
+ au = i4._AttributeMap.get('anAttribute')
+ self.assertEqual(au.dataType(), xsd.normalizedString)
+ self.assertFalse(au.required())
+ # BTW: If you wonder why this works, it's because the
+ # whiteSpace facet on xsd:normalizedString is replace.
+ self.assignAttribute_(i4, self._NotANormalizedString)
+ self.assertEqual(i4.anAttribute, ' multi line tabbed ')
+
+ def testType3 (self): # restrict type
+ i3 = mr.Type3()
+ au = i3._AttributeMap.get('anAttribute')
+ self.assertEqual(au.dataType(), xsd.token)
+ self.assertNotEqual(au, mr.Type4._AttributeMap.get(au.name()))
+ self.assertFalse(au.required())
+ #self.assertRaises(pyxb.BadTypeValueError, self.assignAttribute_, i3, self._NotAToken)
+ self.assignAttribute_(i3, self._NotAnNCName)
+ self.assertEqual(self._NotAnNCName, i3.anAttribute)
+
+ def testType2 (self): # extend isSet
+ i2 = mr.Type2()
+ au = i2._AttributeMap.get('anAttribute')
+ self.assertEqual(au.dataType(), xsd.token)
+ self.assertEqual(au, mr.Type3._AttributeMap.get(au.name()))
+ self.assertFalse(au.required())
+
+ def testType1 (self): # restrict type
+ i1 = mr.Type1()
+ au = i1._AttributeMap.get('anAttribute')
+ self.assertEqual(au.dataType(), xsd.NCName)
+ self.assertFalse(au.required())
+ # The whiteSpace facet on xsd:token is collapse, which does
+ # not remove the interior space.
+ self.assertRaises(pyxb.BadTypeValueError, self.assignAttribute_, i1, self._NotAToken)
+ self.assertRaises(pyxb.BadTypeValueError, self.assignAttribute_, i1, self._NotAnNCName)
+ self.assignAttribute_(i1, self._NCName)
+ self.assertEqual(self._NCName, i1.anAttribute)
+
+ def testRoot (self): # restrict required
+ r = mr.root()
+ rt = type(r)
+ au = rt._AttributeMap.get('anAttribute')
+ self.assertEqual(au.dataType(), xsd.NCName)
+ self.assertTrue(au.required())
+ self.assertRaises(pyxb.MissingAttributeError, r.validateBinding)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/trac/trac-0080/multipleRestriction.xsd b/tests/trac/trac-0080/multipleRestriction.xsd
new file mode 100644
index 0000000..dd47ec7
--- /dev/null
+++ b/tests/trac/trac-0080/multipleRestriction.xsd
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<schema xmlns="http://www.w3.org/2001/XMLSchema"
+ targetNamespace="http://www.example.org/multipleRestriction"
+ xmlns:tns="http://www.example.org/multipleRestriction"
+ elementFormDefault="qualified">
+
+ <complexType name="Type4">
+ <attribute name="anAttribute" type="normalizedString" use="optional" />
+ </complexType>
+
+ <complexType name="Type3">
+ <complexContent>
+ <restriction base="tns:Type4">
+ <attribute name="anAttribute" type="token" use="optional" />
+ </restriction>
+ </complexContent>
+ </complexType>
+
+ <complexType name="Type2">
+ <complexContent>
+ <extension base="tns:Type3">
+ <sequence>
+ <element minOccurs="0" maxOccurs="1" name="isSet" type="boolean" />
+ </sequence>
+ </extension>
+ </complexContent>
+ </complexType>
+
+ <complexType name="Type1">
+ <complexContent>
+ <restriction base="tns:Type2">
+ <sequence>
+ <element minOccurs="0" maxOccurs="1" name="isSet" type="boolean" />
+ </sequence>
+ <attribute name="anAttribute" type="NCName" use="optional" />
+ </restriction>
+ </complexContent>
+ </complexType>
+
+ <element name="root">
+ <complexType>
+ <complexContent>
+ <restriction base="tns:Type1">
+ <sequence>
+ <element minOccurs="0" maxOccurs="1" name="isSet" type="boolean" />
+ </sequence>
+ <attribute name="anAttribute" type="NCName" use="required" />
+ </restriction>
+ </complexContent>
+ </complexType>
+ </element>
+</schema>
+
diff --git a/tests/trac/trac-0080/test.sh b/tests/trac/trac-0080/test.sh
new file mode 100755
index 0000000..b40849b
--- /dev/null
+++ b/tests/trac/trac-0080/test.sh
@@ -0,0 +1,2 @@
+pyxbgen -u multipleRestriction.xsd -m mr
+python check.py
diff --git a/tests/trac/trac-0084/example.xsd b/tests/trac/trac-0084/example.xsd
new file mode 100644
index 0000000..fdc15be
--- /dev/null
+++ b/tests/trac/trac-0084/example.xsd
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xsd:schema
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ targetNamespace="http://schema.example.com/SUBSYSTEM/1.0"
+ xmlns:tns="http://schema.example.com/SUBSYSTEM/1.0"
+ elementFormDefault="qualified"
+ version="1.0">
+
+ <xsd:complexType name="XVendorConfig">
+ <xsd:sequence>
+ <xsd:any namespace="##local" processContents="skip" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+
+</xsd:schema>
diff --git a/tests/trac/trac-0084/test.sh b/tests/trac/trac-0084/test.sh
new file mode 100755
index 0000000..66c9af9
--- /dev/null
+++ b/tests/trac/trac-0084/test.sh
@@ -0,0 +1,10 @@
+pyxbgen -u example.xsd -m example
+
+fail () {
+ echo 1>&2 "${test_name} FAILED: ${@}"
+ exit 1
+}
+
+python tryit.py || fail 'Unable to read generated code'
+echo 'Successfully read code, passed'
+
diff --git a/tests/trac/trac-0084/tryit.py b/tests/trac/trac-0084/tryit.py
new file mode 100644
index 0000000..d2d2de6
--- /dev/null
+++ b/tests/trac/trac-0084/tryit.py
@@ -0,0 +1 @@
+from example import *