ruby/lib/rexml/validation/relaxng.rb

560 строки
15 KiB
Ruby

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}. "
#puts "Next (#@current) is #{@events[@current]}"
#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
#puts "Current isn't a state"
if ( @events[@current].matches?(event) )
@current += 1
if @events[@current].nil?
#puts "#{inspect[0,5]} 1RETURNING #{@previous.inspect[0,5]}"
return @previous.pop
elsif @events[@current].kind_of? State
@current += 1
#puts "#{inspect[0,5]} 2RETURNING (#{@current-1}) #{@events[@current-1].inspect[0,5]}; on return, next is #{@events[@current]}"
@events[@current-1].previous = self
return @events[@current-1]
else
#puts "#{inspect[0,5]} RETURNING self w/ next(#@current) = #{@events[@current]}"
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
#puts "In next with #{event.inspect}."
#puts "events is #{@events.inspect}"
unless @events
@events = []
return nil
end
#puts "current = #@current"
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
#puts "IN CHOICE EXPECTED"
#puts "EVENTS = #{@events.inspect}"
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
#puts "In next with #{event.inspect}."
#puts "events is #{@events.inspect}"
@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
#puts "In next with #{event.inspect}."
#puts "Next (#@current) is #{@events[@current]}"
if ( @events[@current].kind_of? State )
@current += 1
@events[@current-1].previous = self
return @events[@current-1].next( event )
end
#puts "Current isn't a state"
return @previous.pop.next( event ) if @events[@current].nil?
if ( @events[@current].matches?(event) )
@current += 1
if @events[@current].nil?
#puts "#{inspect[0,5]} 1RETURNING self" unless @choices[@choice].nil?
return self unless @choices[@choice].nil?
#puts "#{inspect[0,5]} 1RETURNING #{@previous[-1].inspect[0,5]}"
return @previous.pop
elsif @events[@current].kind_of? State
@current += 1
#puts "#{inspect[0,5]} 2RETURNING (#{@current-1}) #{@events[@current-1].inspect[0,5]}; on return, next is #{@events[@current]}"
@events[@current-1].previous = self
return @events[@current-1]
else
#puts "#{inspect[0,5]} RETURNING self w/ next(#@current) = #{@events[@current]}"
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
#puts "IN CHOICE EXPECTED"
#puts "EVENTS = #{@events.inspect}"
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