Class: SDL4R::Reader
- Inherits:
-
Object
- Object
- SDL4R::Reader
- Defined in:
- lib/sdl4r/reader.rb
Overview
Implementation of a pull parser for SDL designed after the model of Nokogiri::XML::Reader.
Constant Summary collapse
- TYPE_ELEMENT =
:ELEMENT
- TYPE_END_ELEMENT =
:END_ELEMENT
- @@SKIP_PROC =
lambda { |reader| false }
- @@ON_SELF_CLOSING_TAG_PROG =
lambda { |reader| reader.on_self_closing_tag }
- @@comment_handler_set =
{ :INLINE_COMMENT => lambda { |reader| reader.on_simple_comment }, :ONE_LINE_COMMENT => lambda { |reader| reader.on_simple_comment }, :MULTILINE_COMMENT_START => lambda { |reader| reader.on_multiline_comment }, }
- @@common_tag_set =
Handlers that work the same at the top level or in any normal tag body.
{ :WHITESPACE => @@SKIP_PROC, :EOL => @@SKIP_PROC, :SEMICOLON => @@SKIP_PROC, :IDENTIFIER => lambda { |reader| reader.on_tag_start }, }
- @@handler_sets =
The handlers are object with #call() (like Proc, etc) that should return false if the corresponding token is ignored, true otherwise.
{}
- @@value_handlers =
{ :NULL => lambda { |s, reader| nil }, :INTEGER => lambda { |s, reader| reader.parse_integer(s) }, :FLOAT => lambda { |s, reader| reader.parse_float(s) }, :BOOLEAN => lambda { |s, reader| (s =~ /\A(?:true|on)\Z/) ? true : false }, :CHARACTER => lambda { |s, reader| reader.parse_character(s) }, :INLINE_BACKQUOTE_STRING => lambda { |s, reader| s }, :INLINE_DOUBLE_QUOTE_STRING => lambda { |s, reader| reader.parse_double_quote_string(s) }, :MULTILINE_BACKQUOTE_STRING_START => lambda { |s, reader| reader.parse_multiline_backquote_string(s) }, :MULTILINE_DOUBLE_QUOTE_STRING_START => lambda { |s, reader| reader.parse_multiline_double_quote_string(s) }, :INLINE_BINARY => lambda { |s, reader| SdlBinary.decode64(s) }, :MULTILINE_BINARY_START => lambda { |s, reader| reader.parse_multiline_binary(s) }, :DATE => lambda { |s, reader| reader.parse_date(s) }, :TIME_OR_TIMESPAN => lambda { |s, reader| reader.parse_time_span(s) }, }
Instance Attribute Summary collapse
-
#depth ⇒ Object
readonly
Depth of the current SDL node.
-
#name ⇒ Object
readonly
Name of the traversed SDL node.
-
#node_type ⇒ Object
readonly
Type of the traversed SDL node (e.g. TYPE_ELEMENT).
-
#prefix ⇒ Object
readonly
Prefix (namespace) of the traversed SDL node.
Class Method Summary collapse
Instance Method Summary collapse
-
#attribute(prefix, name = nil) ⇒ Object
The value of the specified attribute.
-
#attribute_at(index) ⇒ Object
The value of the attribute at the specified index.
-
#attribute_count ⇒ Integer
Number of attributes in the current element.
-
#attributes ⇒ Array
An array of the attributes structured as follows:
[ [["ns1", "attr1"], 123], [["", "attr2"], true] ]
. -
#attributes? ⇒ boolean
Whether the current element has attributes.
-
#each {|Reader| ... } ⇒ Object
Enumerates all the parsed nodes and calls the given block.
-
#each_tag(only_top_tags = false) {|Tag| ... } ⇒ Object
Calls the given block for each encountered Tag.
-
#initialize(io) ⇒ Reader
constructor
A new instance of Reader.
-
#new_time(year, month, day, hour, min, sec, msec, timezone_code) ⇒ Object
Creates and returns the object representing a datetime (calls SDL4R#new_time by default).
-
#on_anonymous_value ⇒ Object
Should only be called from :top or :tag_body modes.
- #on_attribute ⇒ Object
- #on_eof ⇒ Object
- #on_multiline_comment ⇒ Object
- #on_self_closing_tag ⇒ Object
- #on_simple_comment ⇒ Object
- #on_tag_body_end ⇒ Object
- #on_tag_body_start ⇒ Object
- #on_tag_start ⇒ Object
- #on_value ⇒ Object
- #parse_character(s) ⇒ Object
-
#parse_date(literal) ⇒ Object
Parses the
literal
into a returned Date object. - #parse_double_quote_string(s) ⇒ Object
- #parse_float(s) ⇒ Object
- #parse_integer(s) ⇒ Object
- #parse_multiline_backquote_string(s) ⇒ Object
- #parse_multiline_binary(s) ⇒ Object
- #parse_multiline_double_quote_string(s) ⇒ Object
-
#parse_time_span(literal) ⇒ Object
Parses
literal
(String) into the corresponding SDLTimeSpan, which is then returned. - #raise_unexpected_token ⇒ Object
-
#read ⇒ Reader
Reads the next node in the SDL structure.
- #self_closing? ⇒ Boolean
-
#values ⇒ Object
(also: #value)
The values of the current node, nil if there are none.
- #values? ⇒ Boolean (also: #value?)
Constructor Details
#initialize(io) ⇒ Reader
Returns a new instance of Reader.
154 155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/sdl4r/reader.rb', line 154 def initialize(io) raise ArgumentError, "io == nil" if io.nil? raise ArgumentError, "io is not an IO" unless io.respond_to?(:gets) @io = io @tokenizer = Tokenizer.new(@io) @element = nil @element_pool = [] @depth = 1 clear_node() set_mode(:top) end |
Instance Attribute Details
#depth ⇒ Object (readonly)
Depth of the current SDL node. Depth of top nodes is 1 (0 would be the root that the Reader doesn’t traverse).
152 153 154 |
# File 'lib/sdl4r/reader.rb', line 152 def depth @depth end |
#name ⇒ Object (readonly)
Name of the traversed SDL node.
148 149 150 |
# File 'lib/sdl4r/reader.rb', line 148 def name @name end |
#node_type ⇒ Object (readonly)
Type of the traversed SDL node (e.g. TYPE_ELEMENT).
142 143 144 |
# File 'lib/sdl4r/reader.rb', line 142 def node_type @node_type end |
#prefix ⇒ Object (readonly)
Prefix (namespace) of the traversed SDL node.
145 146 147 |
# File 'lib/sdl4r/reader.rb', line 145 def prefix @prefix end |
Class Method Details
.add_values_handler(map, handler) ⇒ Object
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/sdl4r/reader.rb', line 40 def self.add_values_handler(map, handler) map[:NULL] = handler map[:INTEGER] = handler map[:FLOAT] = handler map[:BOOLEAN] = handler map[:CHARACTER] = handler map[:INLINE_BACKQUOTE_STRING] = handler map[:INLINE_DOUBLE_QUOTE_STRING] = handler map[:MULTILINE_BACKQUOTE_STRING_START] = handler map[:MULTILINE_DOUBLE_QUOTE_STRING_START] = handler map[:INLINE_BINARY] = handler map[:MULTILINE_BINARY_START] = handler map[:DATE] = handler map[:TIME_OR_TIMESPAN] = handler end |
.from_io(io) ⇒ Object
280 281 282 |
# File 'lib/sdl4r/reader.rb', line 280 def self.from_io(io) self.new(io) end |
.from_memory(s) ⇒ Object
284 285 286 |
# File 'lib/sdl4r/reader.rb', line 284 def self.from_memory(s) self.new(StringIO.new(s)) end |
Instance Method Details
#attribute(name) ⇒ Object #attribute(prefix, name) ⇒ Object
Returns the value of the specified attribute.
178 179 180 181 182 183 184 185 186 187 188 189 190 191 |
# File 'lib/sdl4r/reader.rb', line 178 def attribute(prefix, name = nil) return nil unless @element if name prefix, name = prefix.to_s, name.to_s else prefix, name = '', prefix.to_s end @element.attributes.each do |attr| return attr[1] if attr[0][0] == prefix && attr[0][1] == name end return nil end |
#attribute_at(index) ⇒ Object
Returns the value of the attribute at the specified index.
194 195 196 197 198 199 200 |
# File 'lib/sdl4r/reader.rb', line 194 def attribute_at(index) if @element @element.attributes[index] else nil end end |
#attribute_count ⇒ Integer
Returns number of attributes in the current element.
203 204 205 |
# File 'lib/sdl4r/reader.rb', line 203 def attribute_count @element ? @element.attributes.size : 0 end |
#attributes ⇒ Array
Returns an array of the attributes structured as follows: [ [["ns1", "attr1"], 123], [["", "attr2"], true] ]
.
170 171 172 |
# File 'lib/sdl4r/reader.rb', line 170 def attributes @element ? @element.attributes.clone : nil end |
#attributes? ⇒ boolean
Returns whether the current element has attributes.
208 209 210 |
# File 'lib/sdl4r/reader.rb', line 208 def attributes? @element && @element.attributes.size > 0 end |
#each {|Reader| ... } ⇒ Object
Enumerates all the parsed nodes and calls the given block.
299 300 301 302 303 |
# File 'lib/sdl4r/reader.rb', line 299 def each(&block) while node = self.read block.call(node) end end |
#each_tag(only_top_tags = false) {|Tag| ... } ⇒ Object
Calls the given block for each encountered Tag. The block is called when the Tag definition is complete.
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 |
# File 'lib/sdl4r/reader.rb', line 218 def each_tag( = false) stack = [] tag = nil # Only used during definition (values + attributes) while node = read case node.node_type when TYPE_ELEMENT tag = Tag.new @element.prefix, @element.name node.attributes.each do |attribute| tag.set_attribute(attribute[0][0], attribute[0][1], attribute[1]) end values = node.values tag.values = values if values stack.last.add_child(tag) unless stack.empty? if node.self_closing? yield tag if ! or @depth <= 1 else stack << tag end tag = nil # definition ended here when TYPE_END_ELEMENT tag = stack.pop yield tag if ! or depth <= 1 end end end |
#new_time(year, month, day, hour, min, sec, msec, timezone_code) ⇒ Object
Creates and returns the object representing a datetime (calls SDL4R#new_time by default). Can be overriden.
def new_time(year, month, day, hour, min, sec, msec, timezone_code)
Time.utc(year, month, day, hour, min, sec, msec, timezone_code)
end
359 360 361 |
# File 'lib/sdl4r/reader.rb', line 359 def new_time(year, month, day, hour, min, sec, msec, timezone_code) SDL4R::new_time(year, month, day, hour, min, sec, msec, timezone_code) end |
#on_anonymous_value ⇒ Object
Should only be called from :top or :tag_body modes.
437 438 439 440 441 442 |
# File 'lib/sdl4r/reader.rb', line 437 def on_anonymous_value # :nodoc: set_mode :tag_values @element = Element.new '', SDL4R::ANONYMOUS_TAG_NAME on_value end |
#on_attribute ⇒ Object
416 417 418 419 420 421 422 423 424 |
# File 'lib/sdl4r/reader.rb', line 416 def on_attribute # :nodoc: read_name read_equal read_value set_mode :tag_attributes @element.add_attribute(@prefix, @name, @value) false end |
#on_eof ⇒ Object
370 371 372 373 |
# File 'lib/sdl4r/reader.rb', line 370 def on_eof # :nodoc: @node_type = nil true end |
#on_multiline_comment ⇒ Object
376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 |
# File 'lib/sdl4r/reader.rb', line 376 def on_multiline_comment # :nodoc: # @node_type = TYPE_COMMENT @value = @tokenizer.token while @tokenizer.read case @tokenizer.token_type when :EOL @value << ?\n when :MULTILINE_COMMENT_PART @value << @tokenizer.token when :MULTILINE_COMMENT_END @value << @tokenizer.token break else raise_unexpected_token end end false end |
#on_self_closing_tag ⇒ Object
407 408 409 410 411 412 413 |
# File 'lib/sdl4r/reader.rb', line 407 def on_self_closing_tag # :nodoc: @node_type = TYPE_ELEMENT @prefix = @element.prefix @name = @element.name @element.self_closing = true set_mode(@depth <= 1 ? :top : :tag_body) end |
#on_simple_comment ⇒ Object
364 365 366 367 |
# File 'lib/sdl4r/reader.rb', line 364 def on_simple_comment # :nodoc: # @node_type = TYPE_COMMENT false end |
#on_tag_body_end ⇒ Object
456 457 458 459 460 461 462 463 464 465 466 467 |
# File 'lib/sdl4r/reader.rb', line 456 def on_tag_body_end # :nodoc: if @depth <= 1 raise "unexpected end of tag" end clear_node @node_type = TYPE_END_ELEMENT @depth -= 1 set_mode(@depth <= 1 ? :top : :tag_body) true end |
#on_tag_body_start ⇒ Object
445 446 447 448 449 450 451 452 453 |
# File 'lib/sdl4r/reader.rb', line 445 def on_tag_body_start # :nodoc: @node_type = TYPE_ELEMENT @prefix = @element.prefix @name = @element.name @depth += 1 set_mode :tag_body true end |
#on_tag_start ⇒ Object
398 399 400 401 402 403 404 |
# File 'lib/sdl4r/reader.rb', line 398 def on_tag_start # :nodoc: read_name set_mode :tag_values @element = Element.new @prefix, @name false end |
#on_value ⇒ Object
427 428 429 430 431 432 433 |
# File 'lib/sdl4r/reader.rb', line 427 def on_value # :nodoc: read_value set_mode :tag_values @element.add_value(@value) false end |
#parse_character(s) ⇒ Object
548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 |
# File 'lib/sdl4r/reader.rb', line 548 def parse_character(s) case s when /\A.\Z/ s when "\\\\" "\\" when "\\'" "'" when "\\n" "\n" when "\\r" "\r" when "\\t" "\t" else raise "illegal character literal #{s.inspect}" end end |
#parse_date(literal) ⇒ Object
Parses the literal
into a returned Date object.
Raises an ArgumentError if literal
has a bad format.
592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 |
# File 'lib/sdl4r/reader.rb', line 592 def parse_date(literal) # here, we're being stricter than strptime() alone as we forbid trailing chars (also faster) if literal =~ /\A(-?\d+)\/(\d+)\/(\d+)\Z/ date_year = $1.to_i date_month = $2.to_i date_day = $3.to_i skip_whitespaces(false) # Check whether the next tag is the time part if @tokenizer.token_type == :TIME_OR_TIMESPAN # Is it a time or timespan? day, hour, min, sec, msec, zone = parse_time_span_and_time_zone(@tokenizer.token, true, true) if day @tokenizer.unread return Date.civil(date_year, date_month, date_day) else return new_time(date_year, date_month, date_day, hour, min, sec, msec, zone) end else @tokenizer.unread return Date.civil(date_year, date_month, date_day) end else raise ArgumentError, "Malformed Date <#{literal}>" end end |
#parse_double_quote_string(s) ⇒ Object
470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 |
# File 'lib/sdl4r/reader.rb', line 470 def parse_double_quote_string(s) return s if s.empty? string = "" escaped = false s.each_char do |c| if escaped escaped = false case c when "\\", "\"" string << c when "n" string << ?\n when "r" string << ?\r when "t" string << ?\t else @tokenizer.raise_parse_error("Illegal escape character in string literal: '#{c}'.") end elsif c == "\\" escaped = true else string << c end end @tokenizer.raise_parse_error("orphan backslash") if escaped string end |
#parse_float(s) ⇒ Object
577 578 579 580 581 582 583 584 585 |
# File 'lib/sdl4r/reader.rb', line 577 def parse_float(s) if s =~ /\A([^BDF]+)BD\Z/i return BigDecimal($1) elsif s =~ /\A([^BDF]+)[FD]\Z/i return Float($1) rescue @tokenizer.raise_parse_error("not a float '#{$1}'") else return Float(s) rescue @tokenizer.raise_parse_error("not a float '#{s}'") end end |
#parse_integer(s) ⇒ Object
568 569 570 571 572 573 574 |
# File 'lib/sdl4r/reader.rb', line 568 def parse_integer(s) if s =~ /\A([^L]+)L\Z/i return Integer($1) else return Integer(s) end end |
#parse_multiline_backquote_string(s) ⇒ Object
530 531 532 |
# File 'lib/sdl4r/reader.rb', line 530 def parse_multiline_backquote_string(s) parse_multiline_string s, :MULTILINE_BACKQUOTE_STRING_PART, :MULTILINE_BACKQUOTE_STRING_END end |
#parse_multiline_binary(s) ⇒ Object
542 543 544 545 |
# File 'lib/sdl4r/reader.rb', line 542 def parse_multiline_binary(s) literal = parse_multiline_string s, :MULTILINE_BINARY_PART, :MULTILINE_BINARY_END return SdlBinary.decode64(literal) end |
#parse_multiline_double_quote_string(s) ⇒ Object
535 536 537 538 539 |
# File 'lib/sdl4r/reader.rb', line 535 def parse_multiline_double_quote_string(s) parse_double_quote_string( parse_multiline_string( s, :MULTILINE_DOUBLE_QUOTE_STRING_PART, :MULTILINE_DOUBLE_QUOTE_STRING_END)) end |
#parse_time_span(literal) ⇒ Object
Parses literal
(String) into the corresponding SDLTimeSpan, which is then returned.
Raises an ArgumentError if the literal is not a correct timespan literal.
630 631 632 633 634 635 636 637 638 639 |
# File 'lib/sdl4r/reader.rb', line 630 def parse_time_span(literal) days, hours, minutes, seconds, milliseconds, zone_code = parse_time_span_and_time_zone(literal, true, false) if zone_code @tokenizer.raise_parse_error("got a time when expecting a timespan: \"#{literal}\"") end return SDL4R::SdlTimeSpan.new(days || 0, hours, minutes, seconds, milliseconds) end |
#raise_unexpected_token ⇒ Object
337 338 339 340 341 342 |
# File 'lib/sdl4r/reader.rb', line 337 def raise_unexpected_token @tokenizer.raise_parse_error( "unexpected token #{@tokenizer.token_type} #{@tokenizer.token.inspect}", @tokenizer.token_line_no, @tokenizer.token_pos) end |
#read ⇒ Reader
Reads the next node in the SDL structure.
317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 |
# File 'lib/sdl4r/reader.rb', line 317 def read clear_node node = nil while @tokenizer.read handler = @handler_set[@tokenizer.token_type] unless handler raise_unexpected_token end if handler.call(self) node = self if @node_type # otherwise, we reached the end of the file break end end node end |
#self_closing? ⇒ Boolean
249 250 251 |
# File 'lib/sdl4r/reader.rb', line 249 def self_closing? @element ? @element.self_closing : false end |
#values ⇒ Object Also known as: value
Returns the values of the current node, nil if there are none.
261 262 263 264 265 266 267 268 |
# File 'lib/sdl4r/reader.rb', line 261 def values if @element values = @element.values values.empty? ? nil : values.clone else @value end end |
#values? ⇒ Boolean Also known as: value?
271 272 273 274 275 276 277 |
# File 'lib/sdl4r/reader.rb', line 271 def values? if @element !@element.values.empty? else !@value.nil? end end |