summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter A. Bigot <pabigot@users.sourceforge.net>2010-05-30 09:36:04 -0500
committerPeter A. Bigot <pabigot@users.sourceforge.net>2010-05-30 09:36:04 -0500
commitba1715915b1d225b3f7edeaa1abecda6f24d4c3f (patch)
tree5466b3e6b6d5b248e73150e206196428b1eae8f8
parent323bc770cc1cde586bbae55d9139632fb9c2ae9c (diff)
Document validation; ensure bad choices don't destroy the symbol set
-rw-r--r--pyxb/binding/content.py113
1 files changed, 89 insertions, 24 deletions
diff --git a/pyxb/binding/content.py b/pyxb/binding/content.py
index 7f133af..86688e2 100644
--- a/pyxb/binding/content.py
+++ b/pyxb/binding/content.py
@@ -117,9 +117,6 @@ class ContentState_mixin (pyxb.cscRoot):
"""
pass
- def _validate (self, symbol_set, output_sequence):
- raise Exception('ContentState_mixin._validate not implemented in %s' % (type(self),))
-
class ContentModel_mixin (pyxb.cscRoot):
"""Declares methods used by classes representing content model components."""
@@ -133,6 +130,65 @@ class ContentModel_mixin (pyxb.cscRoot):
"""
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{_validateCloneOutputPath}, 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} if the model validates: in this case the symbol set
+ and output sequence have been updated. C{False} if the model
+ requirements cannot be met by the available symbols. In this case
+ C{symbol_set} and C{output_sequence} must be unchanged.
+ """
+ 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.
@@ -853,7 +909,7 @@ class ParticleState (pyxb.cscRoot):
particle = self.__particle
return 'ParticleState(%d:%d,%s:%s)@%x' % (self.__count, particle.minOccurs(), particle.maxOccurs(), particle.term(), id(self))
-class ParticleModel (pyxb.cscRoot):
+class ParticleModel (ContentModel_mixin):
def minOccurs (self): return self.__minOccurs
def maxOccurs (self): return self.__maxOccurs
def term (self): return self.__term
@@ -885,20 +941,25 @@ class ParticleModel (pyxb.cscRoot):
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)
- while (count != self.__maxOccurs) and self.__term._validate(symbol_set, output_sequence):
- #print 'VAL %s old cnt %d, left %s' % (self.__term, count, symbol_set)
- this_size = len(output_sequence)
+ 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
+ # 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
@@ -908,38 +969,41 @@ class _Group (ContentModel_mixin):
def __init__ (self, *particles):
self.__particles = particles
+ 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):
def newState (self, parent_particle_state):
return ChoiceState(self, parent_particle_state)
def _validate (self, symbol_set, output_sequence):
+ reset_mutables = True
for p in self.particles():
- if p._validate(symbol_set, output_sequence):
+ 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):
def newState (self, parent_particle_state):
return AllState(self, parent_particle_state)
- def _validate (self, symbol_set, output_sequence):
- for p in self.particles():
- if not p._validate(symbol_set, output_sequence):
- return False
- return True
-
-
class GroupSequence (_Group):
def newState (self, parent_particle_state):
return SequenceState(self, parent_particle_state)
- def _validate (self, symbol_set, output_sequence):
- for p in self.particles():
- if not p._validate(symbol_set, output_sequence):
- return False
- return True
-
-class Wildcard (ContentState_mixin):
+class Wildcard (ContentState_mixin, ContentModel_mixin):
"""Placeholder for wildcard objects."""
NC_any = '##any' #<<< The namespace constraint "##any"
@@ -987,6 +1051,7 @@ class Wildcard (ContentState_mixin):
nsc = set([ self.__normalizeNamespace(_uri) for _uri in nsc ])
self.__namespaceConstraint = nsc
self.__processContents = kw['process_contents']
+ super(Wildcard, self).__init__(None)
def matches (self, instance, value):
"""Return True iff the value is a valid match against this wildcard.