diff options
Diffstat (limited to 'jni/ruby/lib/rexml/validation')
| -rw-r--r-- | jni/ruby/lib/rexml/validation/relaxng.rb | 538 | ||||
| -rw-r--r-- | jni/ruby/lib/rexml/validation/validation.rb | 143 | ||||
| -rw-r--r-- | jni/ruby/lib/rexml/validation/validationexception.rb | 9 | 
3 files changed, 690 insertions, 0 deletions
| diff --git a/jni/ruby/lib/rexml/validation/relaxng.rb b/jni/ruby/lib/rexml/validation/relaxng.rb new file mode 100644 index 0000000..370efd5 --- /dev/null +++ b/jni/ruby/lib/rexml/validation/relaxng.rb @@ -0,0 +1,538 @@ +require "rexml/validation/validation" +require "rexml/parsers/baseparser" + +module REXML +  module Validation +    # Implemented: +    # * empty +    # * element +    # * attribute +    # * text +    # * optional +    # * choice +    # * oneOrMore +    # * zeroOrMore +    # * group +    # * value +    # * interleave +    # * mixed +    # * ref +    # * grammar +    # * start +    # * define +    # +    # Not implemented: +    # * data +    # * param +    # * include +    # * externalRef +    # * notAllowed +    # * anyName +    # * nsName +    # * except +    # * name +    class RelaxNG +      include Validator + +      INFINITY = 1.0 / 0.0 +      EMPTY = Event.new( nil ) +      TEXT = [:start_element, "text"] +      attr_accessor :current +      attr_accessor :count +      attr_reader :references + +      # FIXME: Namespaces +      def initialize source +        parser = REXML::Parsers::BaseParser.new( source ) + +        @count = 0 +        @references = {} +        @root = @current = Sequence.new(self) +        @root.previous = true +        states = [ @current ] +        begin +          event = parser.pull +          case event[0] +          when :start_element +            case event[1] +            when "empty" +            when "element", "attribute", "text", "value" +              states[-1] << event +            when "optional" +              states << Optional.new( self ) +              states[-2] << states[-1] +            when "choice" +              states << Choice.new( self ) +              states[-2] << states[-1] +            when "oneOrMore" +              states << OneOrMore.new( self ) +              states[-2] << states[-1] +            when "zeroOrMore" +              states << ZeroOrMore.new( self ) +              states[-2] << states[-1] +            when "group" +              states << Sequence.new( self ) +              states[-2] << states[-1] +            when "interleave" +              states << Interleave.new( self ) +              states[-2] << states[-1] +            when "mixed" +              states << Interleave.new( self ) +              states[-2] << states[-1] +              states[-1] << TEXT +            when "define" +              states << [ event[2]["name"] ] +            when "ref" +              states[-1] << Ref.new( event[2]["name"] ) +            when "anyName" +              states << AnyName.new( self ) +              states[-2] << states[-1] +            when "nsName" +            when "except" +            when "name" +            when "data" +            when "param" +            when "include" +            when "grammar" +            when "start" +            when "externalRef" +            when "notAllowed" +            end +          when :end_element +            case event[1] +            when "element", "attribute" +              states[-1] << event +            when "zeroOrMore", "oneOrMore", "choice", "optional", +              "interleave", "group", "mixed" +              states.pop +            when "define" +              ref = states.pop +              @references[ ref.shift ] = ref +            #when "empty" +            end +          when :end_document +            states[-1] << event +          when :text +            states[-1] << event +          end +        end while event[0] != :end_document +      end + +      def receive event +        validate( event ) +      end +    end + +    class State +      def initialize( context ) +        @previous = [] +        @events = [] +        @current = 0 +        @count = context.count += 1 +        @references = context.references +        @value = false +      end + +      def reset +        return if @current == 0 +        @current = 0 +        @events.each {|s| s.reset if s.kind_of? State } +      end + +      def previous=( previous ) +        @previous << previous +      end + +      def next( event ) +        #print "In next with #{event.inspect}.  " +        #p @previous +        return @previous.pop.next( event ) if @events[@current].nil? +        expand_ref_in( @events, @current ) if @events[@current].class == Ref +        if ( @events[@current].kind_of? State ) +          @current += 1 +          @events[@current-1].previous = self +          return @events[@current-1].next( event ) +        end +        if ( @events[@current].matches?(event) ) +          @current += 1 +          if @events[@current].nil? +            return @previous.pop +          elsif @events[@current].kind_of? State +            @current += 1 +            @events[@current-1].previous = self +            return @events[@current-1] +          else +            return self +          end +        else +          return nil +        end +      end + +      def to_s +        # Abbreviated: +        self.class.name =~ /(?:::)(\w)\w+$/ +        # Full: +        #self.class.name =~ /(?:::)(\w+)$/ +        "#$1.#@count" +      end + +      def inspect +        "< #{to_s} #{@events.collect{|e| +          pre = e == @events[@current] ? '#' : '' +          pre + e.inspect unless self == e +        }.join(', ')} >" +      end + +      def expected +        return [@events[@current]] +      end + +      def <<( event ) +        add_event_to_arry( @events, event ) +      end + + +      protected +      def expand_ref_in( arry, ind ) +        new_events = [] +        @references[ arry[ind].to_s ].each{ |evt| +          add_event_to_arry(new_events,evt) +        } +        arry[ind,1] = new_events +      end + +      def add_event_to_arry( arry, evt ) +        evt = generate_event( evt ) +        if evt.kind_of? String +          arry[-1].event_arg = evt if arry[-1].kind_of? Event and @value +          @value = false +        else +          arry << evt +        end +      end + +      def generate_event( event ) +        return event if event.kind_of? State or event.class == Ref +        evt = nil +        arg = nil +        case event[0] +        when :start_element +          case event[1] +          when "element" +            evt = :start_element +            arg = event[2]["name"] +          when "attribute" +            evt = :start_attribute +            arg = event[2]["name"] +          when "text" +            evt = :text +          when "value" +            evt = :text +            @value = true +          end +        when :text +          return event[1] +        when :end_document +          return Event.new( event[0] ) +        else # then :end_element +          case event[1] +          when "element" +            evt = :end_element +          when "attribute" +            evt = :end_attribute +          end +        end +        return Event.new( evt, arg ) +      end +    end + + +    class Sequence < State +      def matches?(event) +        @events[@current].matches?( event ) +      end +    end + + +    class Optional < State +      def next( event ) +        if @current == 0 +          rv = super +          return rv if rv +          @prior = @previous.pop +          return @prior.next( event ) +        end +        super +      end + +      def matches?(event) +        @events[@current].matches?(event) || +        (@current == 0 and @previous[-1].matches?(event)) +      end + +      def expected +        return [ @prior.expected, @events[0] ].flatten if @current == 0 +        return [@events[@current]] +      end +    end + + +    class ZeroOrMore < Optional +      def next( event ) +        expand_ref_in( @events, @current ) if @events[@current].class == Ref +        if ( @events[@current].matches?(event) ) +          @current += 1 +          if @events[@current].nil? +            @current = 0 +            return self +          elsif @events[@current].kind_of? State +            @current += 1 +            @events[@current-1].previous = self +            return @events[@current-1] +          else +            return self +          end +        else +          @prior = @previous.pop +          return @prior.next( event ) if @current == 0 +          return nil +        end +      end + +      def expected +        return [ @prior.expected, @events[0] ].flatten if @current == 0 +        return [@events[@current]] +      end +    end + + +    class OneOrMore < State +      def initialize context +        super +        @ord = 0 +      end + +      def reset +        super +        @ord = 0 +      end + +      def next( event ) +        expand_ref_in( @events, @current ) if @events[@current].class == Ref +        if ( @events[@current].matches?(event) ) +          @current += 1 +          @ord += 1 +          if @events[@current].nil? +            @current = 0 +            return self +          elsif @events[@current].kind_of? State +            @current += 1 +            @events[@current-1].previous = self +            return @events[@current-1] +          else +            return self +          end +        else +          return @previous.pop.next( event ) if @current == 0 and @ord > 0 +          return nil +        end +      end + +      def matches?( event ) +        @events[@current].matches?(event) || +        (@current == 0 and @ord > 0 and @previous[-1].matches?(event)) +      end + +      def expected +        if @current == 0 and @ord > 0 +          return [@previous[-1].expected, @events[0]].flatten +        else +          return [@events[@current]] +        end +      end +    end + + +    class Choice < State +      def initialize context +        super +        @choices = [] +      end + +      def reset +        super +        @events = [] +        @choices.each { |c| c.each { |s| s.reset if s.kind_of? State } } +      end + +      def <<( event ) +        add_event_to_arry( @choices, event ) +      end + +      def next( event ) +        # Make the choice if we haven't +        if @events.size == 0 +          c = 0 ; max = @choices.size +          while c < max +            if @choices[c][0].class == Ref +              expand_ref_in( @choices[c], 0 ) +              @choices += @choices[c] +              @choices.delete( @choices[c] ) +              max -= 1 +            else +              c += 1 +            end +          end +          @events = @choices.find { |evt| evt[0].matches? event } +          # Remove the references +          # Find the events +        end +        unless @events +          @events = [] +          return nil +        end +        super +      end + +      def matches?( event ) +        return @events[@current].matches?( event ) if @events.size > 0 +        !@choices.find{|evt| evt[0].matches?(event)}.nil? +      end + +      def expected +        return [@events[@current]] if @events.size > 0 +        return @choices.collect do |x| +          if x[0].kind_of? State +            x[0].expected +          else +            x[0] +          end +        end.flatten +      end + +      def inspect +        "< #{to_s} #{@choices.collect{|e| e.collect{|f|f.to_s}.join(', ')}.join(' or ')} >" +      end + +      protected +      def add_event_to_arry( arry, evt ) +        if evt.kind_of? State or evt.class == Ref +          arry << [evt] +        elsif evt[0] == :text +         if arry[-1] and +            arry[-1][-1].kind_of?( Event ) and +            arry[-1][-1].event_type == :text and @value + +            arry[-1][-1].event_arg = evt[1] +            @value = false +         end +        else +          arry << [] if evt[0] == :start_element +          arry[-1] << generate_event( evt ) +        end +      end +    end + + +    class Interleave < Choice +      def initialize context +        super +        @choice = 0 +      end + +      def reset +        @choice = 0 +      end + +      def next_current( event ) +        # Expand references +        c = 0 ; max = @choices.size +        while c < max +          if @choices[c][0].class == Ref +            expand_ref_in( @choices[c], 0 ) +            @choices += @choices[c] +            @choices.delete( @choices[c] ) +            max -= 1 +          else +            c += 1 +          end +        end +        @events = @choices[@choice..-1].find { |evt| evt[0].matches? event } +        @current = 0 +        if @events +          # reorder the choices +          old = @choices[@choice] +          idx = @choices.index( @events ) +          @choices[@choice] = @events +          @choices[idx] = old +          @choice += 1 +        end + +        @events = [] unless @events +      end + + +      def next( event ) +        # Find the next series +        next_current(event) unless @events[@current] +        return nil unless @events[@current] + +        expand_ref_in( @events, @current ) if @events[@current].class == Ref +        if ( @events[@current].kind_of? State ) +          @current += 1 +          @events[@current-1].previous = self +          return @events[@current-1].next( event ) +        end +        return @previous.pop.next( event ) if @events[@current].nil? +        if ( @events[@current].matches?(event) ) +          @current += 1 +          if @events[@current].nil? +            return self unless @choices[@choice].nil? +            return @previous.pop +          elsif @events[@current].kind_of? State +            @current += 1 +            @events[@current-1].previous = self +            return @events[@current-1] +          else +            return self +          end +        else +          return nil +        end +      end + +      def matches?( event ) +        return @events[@current].matches?( event ) if @events[@current] +        !@choices[@choice..-1].find{|evt| evt[0].matches?(event)}.nil? +      end + +      def expected +        return [@events[@current]] if @events[@current] +        return @choices[@choice..-1].collect do |x| +          if x[0].kind_of? State +            x[0].expected +          else +            x[0] +          end +        end.flatten +      end + +      def inspect +        "< #{to_s} #{@choices.collect{|e| e.collect{|f|f.to_s}.join(', ')}.join(' and ')} >" +      end +    end + +    class Ref +      def initialize value +        @value = value +      end +      def to_s +        @value +      end +      def inspect +        "{#{to_s}}" +      end +    end +  end +end diff --git a/jni/ruby/lib/rexml/validation/validation.rb b/jni/ruby/lib/rexml/validation/validation.rb new file mode 100644 index 0000000..bab7f22 --- /dev/null +++ b/jni/ruby/lib/rexml/validation/validation.rb @@ -0,0 +1,143 @@ +require 'rexml/validation/validationexception' + +module REXML +  module Validation +    module Validator +      NILEVENT = [ nil ] +      def reset +        @current = @root +        @root.reset +        @root.previous = true +        @attr_stack = [] +        self +      end +      def dump +        puts @root.inspect +      end +      def validate( event ) +        @attr_stack = [] unless defined? @attr_stack +        match = @current.next(event) +        raise ValidationException.new( "Validation error.  Expected: "+ +          @current.expected.join( " or " )+" from #{@current.inspect} "+ +          " but got #{Event.new( event[0], event[1] ).inspect}" ) unless match +        @current = match + +        # Check for attributes +        case event[0] +        when :start_element +          @attr_stack << event[2] +          begin +            sattr = [:start_attribute, nil] +            eattr = [:end_attribute] +            text = [:text, nil] +            k, = event[2].find { |key,value| +              sattr[1] = key +              m = @current.next( sattr ) +              if m +                # If the state has text children... +                if m.matches?( eattr ) +                  @current = m +                else +                  text[1] = value +                  m = m.next( text ) +                  text[1] = nil +                  return false unless m +                  @current = m if m +                end +                m = @current.next( eattr ) +                if m +                  @current = m +                  true +                else +                  false +                end +              else +                false +              end +            } +            event[2].delete(k) if k +          end while k +        when :end_element +          attrs = @attr_stack.pop +          raise ValidationException.new( "Validation error.  Illegal "+ +            " attributes: #{attrs.inspect}") if attrs.length > 0 +        end +      end +    end + +    class Event +      def initialize(event_type, event_arg=nil ) +        @event_type = event_type +        @event_arg = event_arg +      end + +      attr_reader :event_type +      attr_accessor :event_arg + +      def done? +        @done +      end + +      def single? +        return (@event_type != :start_element and @event_type != :start_attribute) +      end + +      def matches?( event ) +        return false unless event[0] == @event_type +        case event[0] +        when nil +          return true +        when :start_element +          return true if event[1] == @event_arg +        when :end_element +          return true +        when :start_attribute +          return true if event[1] == @event_arg +        when :end_attribute +          return true +        when :end_document +          return true +        when :text +          return (@event_arg.nil? or @event_arg == event[1]) +=begin +        when :processing_instruction +          false +        when :xmldecl +          false +        when :start_doctype +          false +        when :end_doctype +          false +        when :externalentity +          false +        when :elementdecl +          false +        when :entity +          false +        when :attlistdecl +          false +        when :notationdecl +          false +        when :end_doctype +          false +=end +        else +          false +        end +      end + +      def ==( other ) +        return false unless other.kind_of? Event +        @event_type == other.event_type and @event_arg == other.event_arg +      end + +      def to_s +        inspect +      end + +      def inspect +        "#{@event_type.inspect}( #@event_arg )" +      end +    end +  end +end diff --git a/jni/ruby/lib/rexml/validation/validationexception.rb b/jni/ruby/lib/rexml/validation/validationexception.rb new file mode 100644 index 0000000..4723d9e --- /dev/null +++ b/jni/ruby/lib/rexml/validation/validationexception.rb @@ -0,0 +1,9 @@ +module REXML +  module Validation +    class ValidationException < RuntimeError +      def initialize msg +        super +      end +    end +  end +end | 
