Class | Kwalify::Yaml::Parser |
In: |
kwalify/parser/yaml.rb
|
Parent: | Kwalify::BaseParser |
YAML parser with validator
ex.
schema = YAML.load_file('schema.yaml') require 'kwalify' validator = Kwalify::Validator.new(schema) parser = Kwalify::Yaml::Parser.new(validator) # validator is optional #parser.preceding_alias = true # optional #parser.data_binding = true # optional ydoc = parser.parse_file('data.yaml') errors = parser.errors if errors && !errors.empty? errors.each do |e| puts "line=#{e.linenum}, path=#{e.path}, mesg=#{e.message}" end end
PRECEDING_ALIAS_PLACEHOLDER | = | Object.new # :nodoc: |
reset | -> | reset_scanner |
data_binding | [RW] | |
errors | [R] | |
mapping_class | [RW] | |
preceding_alias | [RW] | |
sequence_class | [RW] | |
validator | [RW] |
# File kwalify/parser/yaml.rb, line 46 def initialize(validator=nil, properties={}) @validator = validator.is_a?(Hash) ? Kwalify::Validator.new(validator) : validator @data_binding = properties[:data_binding] # enable data binding or not @preceding_alias = properties[:preceding_alias] # allow preceding alias or not @sequence_class = properties[:sequence_class] || Array @mapping_class = properties[:mapping_class] || Hash end
def _validate_error(message, path, linenum=@linenum, column=@column)
#message = _build_message(message_key) error = _error(ValidationError, message.to_s, path, linenum, column) @errors << error
end private :_validate_error
# File kwalify/parser/yaml.rb, line 90 def _set_error_info(linenum=@linenum, column=@column, &block) len = @errors.length yield n = @errors.length - len (1..n).each do |i| error = @errors[-i] error.linenum ||= linenum error.column ||= column error.filename ||= @filename end if n > 0 end
# File kwalify/parser/yaml.rb, line 408 def _validate_map_value(map, map_rule, rule, path, uniq_table, key, val, #*V _linenum, _column) #*V if map_rule && !rule #*V #_validate_error("unknown mapping key.", path) #*V _set_error_info(_linenum, _column) do #*V error = Kwalify::ErrorHelper.validate_error(:key_undefined, #*V rule, path, map, ["#{key}:"]) #*V @errors << error #*V #error.linenum = _linenum #*V #error.column = _column #*V end #*V end #*V _set_error_info(_linenum, _column) do #*V @validator._validate(val, rule, path, @errors, @done, uniq_table, false) #*V end if rule && !val.equal?(PRECEDING_ALIAS_PLACEHOLDER) #*V end
# File kwalify/parser/yaml.rb, line 112 def document_start?() return match?(/---\s/) && @column == 1 end
# File kwalify/parser/yaml.rb, line 795 def location(path) if path.empty? || path == '/' return @location_table[-1] # return value is [linenum, column] end if path.is_a?(Array) items = path.collect { |item| to_scalar(item) } elsif path.is_a?(String) items = path.split('/').collect { |item| to_scalar(item) } items.shift if path[0] == ?/ # delete empty string on head else raise ArgumentError.new("path should be Array or String.") end last_item = items.pop() c = @doc # collection items.each do |item| if c.is_a?(Array) c = c[item.to_i] elsif c.is_a?(Hash) c = c[item] elsif (table = @location_table[c.__id__]) && table[:classobj] if c.respond_to?(item) c = c.__send__(item) elsif c.respond_to?("[]=") c = c[item] else assert false end else #assert false raise ArgumentError.new("#{path.inspect}: invalid path.") end end collection = @location_table[c.__id__] return nil if collection.nil? index = c.is_a?(Array) ? last_item.to_i : last_item return collection[index] # return value is [linenum, column] end
# File kwalify/parser/yaml.rb, line 127 def parse(input=nil, opts={}) reset_scanner(input, opts[:filename], opts[:untabify]) if input return parse_next() end
# File kwalify/parser/yaml.rb, line 198 def parse_alias(rule, path, uniq_table, container) name = group(1) if @anchors.key?(name) val = @anchors[name] elsif @preceding_alias @preceding_aliases << [name, rule, path.dup, container, @linenum, @column - name.length - 1] val = PRECEDING_ALIAS_PLACEHOLDER else raise _syntax_error("*#{name}: anchor not found.", path, @linenum, @column - name.length - 1) end skip_spaces_and_comments() return val end
# File kwalify/parser/yaml.rb, line 187 def parse_anchor(rule, path, uniq_table, container) name = group(1) if @anchors.key?(name) raise _syntax_error("&#{name}: anchor duplicated.", path, @linenum, @column - name.length) end skip_spaces_and_comments() return name end
# File kwalify/parser/yaml.rb, line 426 def parse_block_map(map, map_rule, path, uniq_table) _start_linenum = @linenum #*V _start_column = @column #*V level = @column path.push(nil) is_merged = false while true _linenum = @linenum #*V _column = @column #*V break unless level == @column && scan(MAPKEY_PATTERN) key = group(1) skip_spaces_and_comments() #*V _linenum2 = @linenum #*V _column2 = @column #*V is_merged = _parse_map_value(map, map_rule, path, level, key, is_merged, uniq_table, _linenum, _column, _linenum2, _column2) #skip_spaces_and_comments() end path.pop() _set_error_info(_start_linenum, _start_column) do #*V @validator._validate_mapping_required_keys(map, map_rule, #*V path, @errors) #*V end if map_rule #*V return map end
# File kwalify/parser/yaml.rb, line 463 def parse_block_scalar(rule, path, uniq_table) _linenum = @linenum #*V _column = @column #*V ch = peep(1) if ch == '"' || ch == "'" val = scan_string() scan(/[ \t]*(?:\#.*)?$/) else scan(/(.*?)[ \t]*(?:\#.*)?$/) #str.rstrip! val = to_scalar(group(1)) end val = create_scalar(rule, val, _linenum, _column) #*V #_set_error_info(_linenum, _column) do #*V # @validator._validate_unique(val, rule, path, @errors, uniq_table) #*V #end if uniq_table #*V skip_spaces_and_comments() return val end
# File kwalify/parser/yaml.rb, line 307 def parse_block_seq(seq, seq_rule, path, uniq_table) level = @column rule = seq_rule ? seq_rule.sequence[0] : nil path.push(nil) i = 0 _linenum = @linenum #*V _column = @column #*V uniq_table = rule ? rule._uniqueness_check_table() : nil #*V while level == @column && scan(/-\s+/) path[-1] = i skip_spaces_and_comments() #*V _linenum2 = @linenum _column2 = @column val = parse_block_value(level, rule, path, uniq_table, seq) add_to_seq(rule, seq, val, _linenum2, _column2) # seq << val _set_error_info(_linenum, _column) do #*V @validator._validate(val, rule, path, @errors, @done, uniq_table, false) #*V end if rule && !val.equal?(PRECEDING_ALIAS_PLACEHOLDER) #*V skip_spaces_and_comments() i += 1 _linenum = @linenum #*V _column = @column #*V end path.pop() return seq end
# File kwalify/parser/yaml.rb, line 484 def parse_block_text(column, rule, path, uniq_table) _linenum = @linenum #*V _column = @column #*V indicator = scan(/[|>]/) chomping = scan(/[-+]/) num = scan(/\d+/) indent = num ? column + num.to_i - 1 : nil unless scan(/[ \t]*(.*?)(\#.*)?\r?\n/) # /[ \t]*(\#.*)?\r?\n/ raise _syntax_error("Syntax Error (line break or comment are expected)", path) end s = group(1) is_folded = false while match?(/( *)(.*?)(\r?\n)/) spaces = group(1) text = group(2) nl = group(3) if indent.nil? if spaces.length >= column indent = spaces.length elsif text.empty? s << nl scan(/.*?\n/) next else @diagnostic = 'text indent in block text may be shorter than that of first line or specified column.' break end else if spaces.length < indent && !text.empty? @diagnostic = 'text indent in block text may be shorter than that of first line or specified column.' break end end scan(/.*?\n/) if indicator == '|' s << spaces[indent..-1] if spaces.length >= indent s << text << nl else # indicator == '>' if !text.empty? && spaces.length == indent if s.sub!(/\r?\n((\r?\n)+)\z/, '\1') nil elsif is_folded s.sub!(/\r?\n\z/, ' ') end #s.sub!(/\r?\n\z/, '') if !s.sub!(/\r?\n(\r?\n)+\z/, '\1') && is_folded is_folded = true else is_folded = false s << spaces[indent..-1] if spaces.length > indent end s << text << nl end end ## chomping if chomping == '+' nil elsif chomping == '-' s.sub!(/(\r?\n)+\z/, '') else s.sub!(/(\r?\n)(\r?\n)+\z/, '\1') end # skip_spaces_and_comments() val = s #_set_error_info(_linenum, _column) do #*V # @validator._validate_unique(val, rule, path, @errors, uniq_table) #*V #end if uniq_table #*V return val end
# File kwalify/parser/yaml.rb, line 235 def parse_block_value(level, rule, path, uniq_table, container) skip_spaces_and_comments() ## nil return nil if @column <= level || eos? ## anchor and alias name = nil if scan(/\&([-\w]+)/) name = parse_anchor(rule, path, uniq_table, container) elsif scan(/\*([-\w]+)/) return parse_alias(rule, path, uniq_table, container) end ## type if scan(/!!?\w+/) skip_spaces_and_comments() end ## sequence if match?(/-\s+/) if rule && !rule.sequence #_validate_error("sequence is not expected.", path) rule = nil end seq = create_sequence(rule, @linenum, @column) @anchors[name] = seq if name parse_block_seq(seq, rule, path, uniq_table) return seq end ## mapping if match?(MAPKEY_PATTERN) if rule && !rule.mapping #_validate_error("mapping is not expected.", path) rule = nil end map = create_mapping(rule, @linenum, @column) @anchors[name] = map if name parse_block_map(map, rule, path, uniq_table) return map end ## sequence (flow-style) if match?(/\[/) if rule && !rule.sequence #_validate_error("sequence is not expected.", path) rule = nil end seq = create_sequence(rule, @linenum, @column) @anchors[name] = seq if name parse_flow_seq(seq, rule, path, uniq_table) return seq end ## mapping (flow-style) if match?(/\{/) if rule && !rule.mapping #_validate_error("mapping is not expected.", path) rule = nil end map = create_mapping(rule, @linenum, @column) @anchors[name] = map if name parse_flow_map(map, rule, path, uniq_table) return map end ## block text if match?(/[|>]/) text = parse_block_text(level, rule, path, uniq_table) @anchors[name] = text if name return text end ## scalar scalar = parse_block_scalar(rule, path, uniq_table) @anchors[name] = scalar if name return scalar end
# File kwalify/parser/yaml.rb, line 133 def parse_file(filename, opts={}) opts[:filename] = filename return parse(File.read(filename), opts) end
# File kwalify/parser/yaml.rb, line 634 def parse_flow_map(map, map_rule, path, uniq_table) #scan(/\{\s*/) # not work? _start_linenum = @linenum #*V _start_column = @column #*V scan(/\{/) skip_spaces_and_comments() if scan(/\}/) nil else path.push(nil) is_merged = false while true _linenum = @linenum #*V _column = @column #*V unless scan(MAPKEY_PATTERN) raise _syntax_error("mapping key is expected.", path) end key = group(1) skip_spaces_and_comments() _linenum2 = @linenum #*V _column2 = @column #*V is_merged = _parse_map_value(map, map_rule, path, nil, key, is_merged, uniq_table, _linenum, _column, _linenum2, _column2) #skip_spaces_and_comments() break unless scan(/,\s+/) end path.pop() unless scan(/\}/) raise _syntax_error("flow mapping is not closed by '}'.", path) end end skip_spaces_and_comments() _set_error_info(_start_linenum, _start_column) do #*V @validator._validate_mapping_required_keys(map, map_rule, path, @errors) #*V end if map_rule #*V return map end
# File kwalify/parser/yaml.rb, line 673 def parse_flow_scalar(rule, path, uniq_table) ch = peep(1) _linenum = @linenum #*V _column = @column #*V if ch == '"' || ch == "'" val = scan_string() else str = scan(/[^,\]\}\#]*/) if match?(/,\S/) while match?(/,\S/) str << scan(/./) str << scan(/[^,\]\}\#]*/) end end str.rstrip! val = to_scalar(str) end val = create_scalar(rule, val, _linenum, _column) #*V #_set_error_info(_linenum, _column) do #*V # @validator._validate_unique(val, rule, path, @errors, uniq_table) #*V #end if uniq_table #*V skip_spaces_and_comments() return val end
# File kwalify/parser/yaml.rb, line 597 def parse_flow_seq(seq, seq_rule, path, uniq_table) #scan(/\[\s*/) scan(/\[/) skip_spaces_and_comments() if scan(/\]/) nil else rule = seq_rule ? seq_rule.sequence[0] : nil #*V uniq_table = rule ? rule._uniqueness_check_table() : nil #*V path.push(nil) i = 0 while true path[-1] = i _linenum = @linenum #*V _column = @column #*V val = parse_flow_value(rule, path, uniq_table, seq) add_to_seq(rule, seq, val, _linenum, _column) # seq << val _set_error_info(_linenum, _column) do #*V @validator._validate(val, rule, path, @errors, @done, uniq_table, false) #*V end if rule && !val.equal?(PRECEDING_ALIAS_PLACEHOLDER) #*V skip_spaces_and_comments() break unless scan(/,\s+/) i += 1 if match?(/\]/) raise _syntax_error("sequence item required (or last comma is extra).", path) end end path.pop() unless scan(/\]/) raise _syntax_error("flow sequence is not closed by ']'.", path) end end skip_spaces_and_comments() return seq end
# File kwalify/parser/yaml.rb, line 555 def parse_flow_value(rule, path, uniq_table, container) skip_spaces_and_comments() ## anchor and alias name = nil if scan(/\&([-\w]+)/) name = parse_anchor(rule, path, uniq_table, container) elsif scan(/\*([-\w]+)/) return parse_alias(rule, path, uniq_table, container) end ## type if scan(/!!?\w+/) skip_spaces_and_comments() end ## sequence if match?(/\[/) if rule && !rule.sequence #*V #_validate_error("sequence is not expected.", path) #*V rule = nil #*V end #*V seq = create_sequence(rule, @linenum, @column) @anchors[name] = seq if name parse_flow_seq(seq, rule, path, uniq_table) return seq end ## mapping if match?(/\{/) if rule && !rule.mapping #*V #_validate_error("mapping is not expected.", path) #*V rule = nil #*V end #*V map = create_mapping(rule, @linenum, @column) @anchors[name] = map if name parse_flow_map(map, rule, path, uniq_table) return map end ## scalar scalar = parse_flow_scalar(rule, path, uniq_table) @anchors[name] = scalar if name return scalar end
# File kwalify/parser/yaml.rb, line 139 def parse_next() reset_parser() path = [] skip_spaces_and_comments() if document_start?() scan(/.*\n/) skip_spaces_and_comments() end _linenum = @linenum #*V _column = @column #*V rule = @validator ? @validator.rule : nil #*V uniq_table = nil #*V parent = nil #*V val = parse_block_value(0, rule, path, uniq_table, parent) _set_error_info(_linenum, _column) do #*V @validator._validate(val, rule, [], @errors, @done, uniq_table, false) #*V end if rule #*V resolve_preceding_aliases(val) if @preceding_alias unless eos? || document_start?() || stream_end?() raise _syntax_error("document end expected (maybe invalid tab char found).", path) end @doc = val @location_table[-1] = [_linenum, _column] return val end
# File kwalify/parser/yaml.rb, line 166 def parse_stream(input, opts={}, &block) reset_scanner(input, opts[:filename], opts[:untabify]) ydocs = block_given? ? nil : [] while true ydoc = parse_next() ydocs ? (ydocs << ydoc) : (yield ydoc) break if eos? || stream_end?() document_start?() or raise "** internal error" scan(/.*\n/) end return ydocs end
# File kwalify/parser/yaml.rb, line 60 def reset_parser() @anchors = {} @errors = [] @done = {} @preceding_aliases = [] @location_table = {} # object_id -> sequence or mapping @doc = nil end
# File kwalify/parser/yaml.rb, line 215 def resolve_preceding_aliases(val) @preceding_aliases.each do |name, rule, path, container, _linenum, _column| unless @anchors.key?(name) raise _syntax_error("*#{name}: anchor not found.", path, _linenum, _column) end key = path[-1] val = @anchors[name] raise unless !container.respond_to?('[]') || container[key].equal?(PRECEDING_ALIAS_PLACEHOLDER) if container.is_a?(Array) container[key] = val else put_to_map(rule, container, key, val, _linenum, _column) end _set_error_info(_linenum, _column) do #*V @validator._validate(val, rule, path, @errors, @done, false) #*V end if rule #*V end end
# File kwalify/parser/yaml.rb, line 834 def set_errors_linenum(errors) errors.each do |error| error.linenum, error.column = location(error.path) end end
# File kwalify/parser/yaml.rb, line 103 def skip_spaces_and_comments() scan(/\s+/) while match?(/\#/) scan(/.*?\n/) scan(/\s+/) end end
# File kwalify/parser/yaml.rb, line 117 def stream_end?() return match?(/\.\.\.\s/) && @column == 1 end
# File kwalify/parser/yaml.rb, line 702 def to_scalar(str) case str when nil ; val = nil when /\A-?\d+\.\d+\z/ ; val = str.to_f when /\A-?\d+\z/ ; val = str.to_i when /\A(true|yes)\z/ ; val = true when /\A(false|no)\z/ ; val = false when /\A(null|~)\z/ ; val = nil when /\A"(.*)"\z/ ; val = $1 when /\A'(.*)'\z/ ; val = $1 when /\A:(\w+)\z/ ; val = $1.intern when /\A(\d\d\d\d)-(\d\d)-(\d\d)(?: (\d\d):(\d\d):(\d\d))?\z/ year, month, day, hour, min, sec = $1, $2, $3, $4, $5, $6 if hour val = Time.mktime(year, month, day, hour, min, sec) else val = Date.new(year.to_i, month.to_i, day.to_i) end ## or #params = [$1, $2, $3, $4, $5, $6] #val = Time.mktime(*params) else val = str.empty? ? nil : str end skip_spaces_and_comments() return val end
# File kwalify/parser/yaml.rb, line 783 def _getclass(classname) mod = Object classname.split(/::/).each do |modname| mod = mod.const_get(modname) # raises NameError when module not found end return mod end
# File kwalify/parser/yaml.rb, line 762 def add_to_seq(rule, seq, val, linenum, column) seq << val @location_table[seq.__id__] << [linenum, column] end
# File kwalify/parser/yaml.rb, line 743 def create_mapping(rule, linenum, column) if rule && rule.classobj && @data_binding classobj = rule.classobj map = classobj.new else classobj = nil map = @mapping_class.new # {} end @location_table[map.__id__] = hash = {} hash[:classobj] = classobj if classobj return map end
# File kwalify/parser/yaml.rb, line 757 def create_scalar(rule, value, linenum, column) return value end
# File kwalify/parser/yaml.rb, line 736 def create_sequence(rule, linenum, column) seq = @sequence_class.new @location_table[seq.__id__] = [] return seq end
# File kwalify/parser/yaml.rb, line 768 def put_to_map(rule, map, key, val, linenum, column) #if map.is_a?(Hash) # map[key] = val #elsif map.respond_to?(name="#{key}=") # map.__send__(name, val) #elsif map.respond_to?('[]=') # map[key] = val #else # map.instance_variable_set("@#{key}", val) #end map[key] = val @location_table[map.__id__][key] = [linenum, column] end