diff options
author | Peter A. Bigot <pabigot@users.sourceforge.net> | 2010-05-30 09:36:04 -0500 |
---|---|---|
committer | Peter A. Bigot <pabigot@users.sourceforge.net> | 2010-05-30 09:36:04 -0500 |
commit | ba1715915b1d225b3f7edeaa1abecda6f24d4c3f (patch) | |
tree | 5466b3e6b6d5b248e73150e206196428b1eae8f8 | |
parent | 323bc770cc1cde586bbae55d9139632fb9c2ae9c (diff) |
Document validation; ensure bad choices don't destroy the symbol set
-rw-r--r-- | pyxb/binding/content.py | 113 |
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. |