sdl4r 0.9.9 → 0.9.11
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +70 -1
- data/LICENSE +499 -497
- data/Rakefile +38 -28
- data/TODO +194 -45
- data/doc/classes/SDL4R/AbbreviationTimezoneProxy.html +151 -0
- data/doc/classes/SDL4R/ConstantTimezone.html +129 -0
- data/doc/classes/SDL4R/Element.html +148 -0
- data/doc/classes/SDL4R/Reader.html +683 -0
- data/doc/classes/SDL4R/RelativeTimezone.html +187 -0
- data/doc/classes/SDL4R/SdlBinary.html +188 -0
- data/doc/classes/SDL4R/SdlParseError.html +110 -0
- data/doc/classes/SDL4R/SdlTimeSpan.html +651 -0
- data/doc/classes/SDL4R/Serializer.html +557 -0
- data/doc/classes/SDL4R/Serializer/Ref.html +138 -0
- data/doc/classes/SDL4R/TZAbbreviationDB/Record.html +117 -0
- data/doc/classes/SDL4R/Tag.html +1274 -0
- data/doc/classes/SDL4R/Token.html +131 -0
- data/doc/created.rid +1 -0
- data/doc/files/CHANGELOG.html +290 -0
- data/doc/files/LICENSE.html +53 -0
- data/doc/files/README.html +405 -0
- data/doc/files/lib/sdl4r/abbreviation_timezone_proxy_rb.html +63 -0
- data/doc/files/lib/sdl4r/constant_timezone_rb.html +54 -0
- data/doc/files/lib/sdl4r/element_rb.html +54 -0
- data/doc/files/lib/sdl4r/reader_rb.html +68 -0
- data/doc/files/lib/sdl4r/relative_timezone_rb.html +62 -0
- data/doc/files/lib/sdl4r/sdl4r_rb.html +66 -0
- data/doc/files/lib/sdl4r/sdl4r_tzinfo_rb.html +64 -0
- data/doc/files/lib/sdl4r/sdl4r_version_rb.html +54 -0
- data/doc/files/lib/sdl4r/sdl_binary_rb.html +54 -0
- data/doc/files/lib/sdl4r/sdl_parse_error_rb.html +54 -0
- data/doc/files/lib/sdl4r/sdl_time_span_rb.html +54 -0
- data/doc/files/lib/sdl4r/serializer_rb.html +62 -0
- data/doc/files/lib/sdl4r/tag_rb.html +66 -0
- data/doc/files/lib/sdl4r/token_rb.html +54 -0
- data/doc/files/lib/sdl4r/tokenizer_rb.html +64 -0
- data/doc/files/lib/sdl4r/tz_abbreviation_db_rb.html +67 -0
- data/doc/files/lib/sdl4r_rb.html +54 -0
- data/doc/files/lib/sdl4r_tzinfo_rb.html +54 -0
- data/doc/fr_class_index.html +23 -0
- data/doc/fr_file_index.html +40 -0
- data/doc/fr_method_index.html +4711 -0
- data/doc/index.html +15 -0
- data/doc/rdoc-style.css +328 -0
- data/lib/sdl4r.rb +3 -1
- data/lib/sdl4r/abbreviation_timezone_proxy.rb +38 -0
- data/lib/sdl4r/constant_timezone.rb +58 -0
- data/{test/sdl4r/sdl_test.rb → lib/sdl4r/element.rb} +19 -14
- data/lib/sdl4r/reader.rb +772 -0
- data/lib/sdl4r/relative_timezone.rb +156 -0
- data/lib/sdl4r/sdl4r.rb +187 -45
- data/lib/sdl4r/sdl4r_tzinfo.rb +75 -0
- data/lib/sdl4r/sdl4r_version.rb +24 -0
- data/lib/sdl4r/sdl_parse_error.rb +1 -1
- data/lib/sdl4r/sdl_time_span.rb +5 -1
- data/lib/sdl4r/serializer.rb +473 -60
- data/lib/sdl4r/tag.rb +126 -51
- data/lib/sdl4r/token.rb +49 -0
- data/lib/sdl4r/tokenizer.rb +431 -0
- data/lib/sdl4r/tz_abbreviation_db.rb +266 -0
- data/read_jprof.html +16934 -0
- data/read_jprof_pull.html +14451 -0
- data/read_prof.html +4983 -0
- data/read_prof_pull.html +4896 -0
- data/test/sdl4r/parser_test.rb +577 -503
- data/test/sdl4r/read_jprof.rb +58 -0
- data/test/sdl4r/read_prof.rb +70 -0
- data/test/sdl4r/reader_test.rb +173 -0
- data/test/sdl4r/relative_timezone_test.rb +102 -0
- data/test/sdl4r/sdl4r_test.rb +611 -528
- data/test/sdl4r/sdl4r_tzinfo_test.rb +108 -0
- data/test/sdl4r/sdl_test_case.rb +60 -0
- data/test/sdl4r/serializer_test.rb +448 -11
- data/test/sdl4r/tag_test.rb +84 -5
- data/test/sdl4r/tokenizer_test.rb +128 -0
- metadata +69 -11
- data/lib/sdl4r/parser.rb +0 -648
- data/lib/sdl4r/parser/reader.rb +0 -184
- data/lib/sdl4r/parser/time_span_with_zone.rb +0 -57
- data/lib/sdl4r/parser/token.rb +0 -138
- data/lib/sdl4r/parser/tokenizer.rb +0 -507
@@ -19,24 +19,29 @@
|
|
19
19
|
#++
|
20
20
|
|
21
21
|
module SDL4R
|
22
|
+
# Used internally by Reader for keeping track of its state.
|
23
|
+
# It shouldn't be used directly as it is subject to changes as Reader is modified.
|
24
|
+
class Element
|
22
25
|
|
23
|
-
|
24
|
-
|
26
|
+
attr_accessor :self_closing
|
27
|
+
attr_reader :name, :prefix, :attributes, :values
|
25
28
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
+
def initialize(prefix, name)
|
30
|
+
@prefix = prefix
|
31
|
+
@name = name
|
32
|
+
@attributes = []
|
33
|
+
@values = []
|
34
|
+
@self_closing = false
|
35
|
+
end
|
29
36
|
|
30
|
-
def
|
31
|
-
|
32
|
-
|
33
|
-
assert_raise ArgumentError do tag.add_value(Object.new) end
|
34
|
-
assert_raise ArgumentError do tag.add_value([1, 2, 3]) end
|
35
|
-
assert_raise ArgumentError do tag.add_value({"a" => "b"}) end
|
37
|
+
def add_attribute(prefix, name, value)
|
38
|
+
@attributes << [[prefix, name], value]
|
39
|
+
end
|
36
40
|
|
37
|
-
|
38
|
-
|
39
|
-
assert_equal [0.3], tag.values
|
41
|
+
def add_value(value)
|
42
|
+
@values << value
|
40
43
|
end
|
44
|
+
|
45
|
+
self.freeze
|
41
46
|
end
|
42
47
|
end
|
data/lib/sdl4r/reader.rb
ADDED
@@ -0,0 +1,772 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
# encoding: UTF-8
|
3
|
+
|
4
|
+
#--
|
5
|
+
# Simple Declarative Language (SDL) for Ruby
|
6
|
+
# Copyright 2005 Ikayzo, inc.
|
7
|
+
#
|
8
|
+
# This program is free software. You can distribute or modify it under the
|
9
|
+
# terms of the GNU Lesser General Public License version 2.1 as published by
|
10
|
+
# the Free Software Foundation.
|
11
|
+
#
|
12
|
+
# This program is distributed AS IS and WITHOUT WARRANTY. OF ANY KIND,
|
13
|
+
# INCLUDING MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
|
14
|
+
# See the GNU Lesser General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU Lesser General Public License
|
17
|
+
# along with this program; if not, contact the Free Software Foundation, Inc.,
|
18
|
+
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
19
|
+
#++
|
20
|
+
|
21
|
+
module SDL4R
|
22
|
+
|
23
|
+
require 'stringio'
|
24
|
+
require 'date'
|
25
|
+
|
26
|
+
require 'sdl4r/sdl4r'
|
27
|
+
require 'sdl4r/sdl_time_span'
|
28
|
+
require 'sdl4r/sdl_binary'
|
29
|
+
require 'sdl4r/tokenizer'
|
30
|
+
require 'sdl4r/element'
|
31
|
+
|
32
|
+
# Implementation of a pull parser for SDL designed after the model of Nokogiri::XML::Reader.
|
33
|
+
#
|
34
|
+
class Reader
|
35
|
+
|
36
|
+
TYPE_ELEMENT = :ELEMENT
|
37
|
+
TYPE_END_ELEMENT = :END_ELEMENT
|
38
|
+
|
39
|
+
# @private
|
40
|
+
def self.add_values_handler(map, handler)
|
41
|
+
map[:NULL] = handler
|
42
|
+
map[:INTEGER] = handler
|
43
|
+
map[:FLOAT] = handler
|
44
|
+
map[:BOOLEAN] = handler
|
45
|
+
map[:CHARACTER] = handler
|
46
|
+
map[:INLINE_BACKQUOTE_STRING] = handler
|
47
|
+
map[:INLINE_DOUBLE_QUOTE_STRING] = handler
|
48
|
+
map[:MULTILINE_BACKQUOTE_STRING_START] = handler
|
49
|
+
map[:MULTILINE_DOUBLE_QUOTE_STRING_START] = handler
|
50
|
+
map[:INLINE_BINARY] = handler
|
51
|
+
map[:MULTILINE_BINARY_START] = handler
|
52
|
+
map[:DATE] = handler
|
53
|
+
map[:TIME_OR_TIMESPAN] = handler
|
54
|
+
end
|
55
|
+
|
56
|
+
# @private
|
57
|
+
@@SKIP_PROC = lambda { |reader| false } # skips current token
|
58
|
+
# @private
|
59
|
+
@@ON_SELF_CLOSING_TAG_PROG = lambda { |reader| reader.on_self_closing_tag }
|
60
|
+
|
61
|
+
# @private
|
62
|
+
@@comment_handler_set = {
|
63
|
+
:INLINE_COMMENT => lambda { |reader| reader.on_simple_comment },
|
64
|
+
:ONE_LINE_COMMENT => lambda { |reader| reader.on_simple_comment },
|
65
|
+
:MULTILINE_COMMENT_START => lambda { |reader| reader.on_multiline_comment },
|
66
|
+
}
|
67
|
+
|
68
|
+
# Handlers that work the same at the top level or in any normal tag body.
|
69
|
+
# @private
|
70
|
+
@@common_tag_set = {
|
71
|
+
:WHITESPACE => @@SKIP_PROC,
|
72
|
+
:EOL => @@SKIP_PROC,
|
73
|
+
:SEMICOLON => @@SKIP_PROC,
|
74
|
+
:IDENTIFIER => lambda { |reader| reader.on_tag_start },
|
75
|
+
}
|
76
|
+
|
77
|
+
# The handlers are object with #call() (like Proc, etc) that should return false if the
|
78
|
+
# corresponding token is ignored, true otherwise.
|
79
|
+
# @private
|
80
|
+
#
|
81
|
+
@@handler_sets = {}
|
82
|
+
|
83
|
+
@@handler_sets[:top] = {
|
84
|
+
:EOF => lambda { |reader| reader.on_eof },
|
85
|
+
}
|
86
|
+
@@handler_sets[:top].merge!(@@common_tag_set)
|
87
|
+
@@handler_sets[:top].merge!(@@comment_handler_set)
|
88
|
+
add_values_handler(@@handler_sets[:top], lambda { |reader| reader.on_anonymous_value })
|
89
|
+
|
90
|
+
@@handler_sets[:tag_values] = {
|
91
|
+
:WHITESPACE => @@SKIP_PROC,
|
92
|
+
:LINE_CONTINUATION => @@SKIP_PROC,
|
93
|
+
:IDENTIFIER => lambda { |reader| reader.on_attribute },
|
94
|
+
:EOL => @@ON_SELF_CLOSING_TAG_PROG,
|
95
|
+
:SEMICOLON => @@ON_SELF_CLOSING_TAG_PROG,
|
96
|
+
:BLOCK_START => lambda { |reader| reader.on_tag_body_start },
|
97
|
+
:EOF => @@ON_SELF_CLOSING_TAG_PROG,
|
98
|
+
}
|
99
|
+
@@handler_sets[:tag_values].merge!(@@comment_handler_set)
|
100
|
+
add_values_handler(@@handler_sets[:tag_values], lambda { |reader| reader.on_value })
|
101
|
+
|
102
|
+
@@handler_sets[:tag_attributes] = {
|
103
|
+
:WHITESPACE => @@SKIP_PROC,
|
104
|
+
:LINE_CONTINUATION => @@SKIP_PROC,
|
105
|
+
:IDENTIFIER => lambda { |reader| reader.on_attribute },
|
106
|
+
:EOL => @@ON_SELF_CLOSING_TAG_PROG,
|
107
|
+
:SEMICOLON => @@ON_SELF_CLOSING_TAG_PROG,
|
108
|
+
:BLOCK_START => lambda { |reader| reader.on_tag_body_start },
|
109
|
+
:EOF => @@ON_SELF_CLOSING_TAG_PROG,
|
110
|
+
}
|
111
|
+
@@handler_sets[:tag_attributes].merge!(@@comment_handler_set)
|
112
|
+
|
113
|
+
@@handler_sets[:tag_body] = {
|
114
|
+
:BLOCK_END => lambda { |reader| reader.on_tag_body_end },
|
115
|
+
}
|
116
|
+
@@handler_sets[:tag_body].merge!(@@common_tag_set)
|
117
|
+
@@handler_sets[:tag_body].merge!(@@comment_handler_set)
|
118
|
+
add_values_handler(@@handler_sets[:tag_body], lambda { |reader| reader.on_anonymous_value })
|
119
|
+
|
120
|
+
@@handler_sets[:eof] = {}
|
121
|
+
|
122
|
+
# @private
|
123
|
+
@@value_handlers = {
|
124
|
+
:NULL => lambda { |s, reader| nil },
|
125
|
+
:INTEGER => lambda { |s, reader| reader.parse_integer(s) },
|
126
|
+
:FLOAT => lambda { |s, reader| reader.parse_float(s) },
|
127
|
+
:BOOLEAN => lambda { |s, reader| (s =~ /\A(?:true|on)\Z/) ? true : false },
|
128
|
+
:CHARACTER => lambda { |s, reader| reader.parse_character(s) },
|
129
|
+
:INLINE_BACKQUOTE_STRING => lambda { |s, reader| s },
|
130
|
+
:INLINE_DOUBLE_QUOTE_STRING => lambda { |s, reader| reader.parse_double_quote_string(s) },
|
131
|
+
:MULTILINE_BACKQUOTE_STRING_START =>
|
132
|
+
lambda { |s, reader| reader.parse_multiline_backquote_string(s) },
|
133
|
+
:MULTILINE_DOUBLE_QUOTE_STRING_START =>
|
134
|
+
lambda { |s, reader| reader.parse_multiline_double_quote_string(s) },
|
135
|
+
:INLINE_BINARY => lambda { |s, reader| SdlBinary.decode64(s) },
|
136
|
+
:MULTILINE_BINARY_START => lambda { |s, reader| reader.parse_multiline_binary(s) },
|
137
|
+
:DATE => lambda { |s, reader| reader.parse_date(s) },
|
138
|
+
:TIME_OR_TIMESPAN => lambda { |s, reader| reader.parse_time_span(s) },
|
139
|
+
}
|
140
|
+
|
141
|
+
# Type of the traversed SDL node (e.g. TYPE_ELEMENT).
|
142
|
+
attr_reader :node_type
|
143
|
+
|
144
|
+
# Prefix (namespace) of the traversed SDL node.
|
145
|
+
attr_reader :prefix
|
146
|
+
|
147
|
+
# Name of the traversed SDL node.
|
148
|
+
attr_reader :name
|
149
|
+
|
150
|
+
# Depth of the current SDL node. Depth of top nodes is 1 (0 would be the root that the Reader
|
151
|
+
# doesn't traverse).
|
152
|
+
attr_reader :depth
|
153
|
+
|
154
|
+
def initialize(io)
|
155
|
+
raise ArgumentError, "io == nil" if io.nil?
|
156
|
+
raise ArgumentError, "io is not an IO" unless io.respond_to?(:gets)
|
157
|
+
|
158
|
+
@io = io
|
159
|
+
@tokenizer = Tokenizer.new(@io)
|
160
|
+
@element = nil
|
161
|
+
@element_pool = []
|
162
|
+
@depth = 1
|
163
|
+
|
164
|
+
clear_node()
|
165
|
+
set_mode(:top)
|
166
|
+
end
|
167
|
+
|
168
|
+
# @return [Array] an array of the attributes structured as follows:
|
169
|
+
# <code>[ [["ns1", "attr1"], 123], [["", "attr2"], true] ]</code>
|
170
|
+
def attributes
|
171
|
+
@element ? @element.attributes.clone : nil
|
172
|
+
end
|
173
|
+
|
174
|
+
# @return the value of the specified attribute.
|
175
|
+
#
|
176
|
+
# @overload attribute(name)
|
177
|
+
# @overload attribute (prefix, name)
|
178
|
+
def attribute(prefix, name = nil)
|
179
|
+
return nil unless @element
|
180
|
+
|
181
|
+
if name
|
182
|
+
prefix, name = prefix.to_s, name.to_s
|
183
|
+
else
|
184
|
+
prefix, name = '', prefix.to_s
|
185
|
+
end
|
186
|
+
|
187
|
+
@element.attributes.each do |attr|
|
188
|
+
return attr[1] if attr[0][0] == prefix && attr[0][1] == name
|
189
|
+
end
|
190
|
+
return nil
|
191
|
+
end
|
192
|
+
|
193
|
+
# @return the value of the attribute at the specified index.
|
194
|
+
def attribute_at(index)
|
195
|
+
if @element
|
196
|
+
@element.attributes[index]
|
197
|
+
else
|
198
|
+
nil
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
# @return [Integer] number of attributes in the current element
|
203
|
+
def attribute_count
|
204
|
+
@element ? @element.attributes.size : 0
|
205
|
+
end
|
206
|
+
|
207
|
+
# @return [boolean] whether the current element has attributes.
|
208
|
+
def attributes?
|
209
|
+
@element && @element.attributes.size > 0
|
210
|
+
end
|
211
|
+
|
212
|
+
# Calls the given block for each encountered Tag. The block is called when the Tag definition
|
213
|
+
# is complete.
|
214
|
+
#
|
215
|
+
# @param [boolean] only_top_tags if true only top Tags are enumerated
|
216
|
+
# @yield [Tag] called at each Tag
|
217
|
+
#
|
218
|
+
def each_tag(only_top_tags = false)
|
219
|
+
stack = []
|
220
|
+
tag = nil # Only used during definition (values + attributes)
|
221
|
+
|
222
|
+
while node = read
|
223
|
+
case node.node_type
|
224
|
+
|
225
|
+
when TYPE_ELEMENT
|
226
|
+
tag = Tag.new @element.prefix, @element.name
|
227
|
+
node.attributes.each do |attribute|
|
228
|
+
tag.set_attribute(attribute[0][0], attribute[0][1], attribute[1])
|
229
|
+
end
|
230
|
+
values = node.values
|
231
|
+
tag.values = values if values
|
232
|
+
stack.last.add_child(tag) unless stack.empty?
|
233
|
+
|
234
|
+
if node.self_closing?
|
235
|
+
yield tag if !only_top_tags or @depth <= 1
|
236
|
+
else
|
237
|
+
stack << tag
|
238
|
+
end
|
239
|
+
|
240
|
+
tag = nil # definition ended here
|
241
|
+
|
242
|
+
when TYPE_END_ELEMENT
|
243
|
+
tag = stack.pop
|
244
|
+
yield tag if !only_top_tags or depth <= 1
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
def self_closing?
|
250
|
+
@element ? @element.self_closing : false
|
251
|
+
end
|
252
|
+
|
253
|
+
def clear_node
|
254
|
+
@node_type = nil
|
255
|
+
@prefix = nil
|
256
|
+
@name = nil
|
257
|
+
end
|
258
|
+
private :clear_node
|
259
|
+
|
260
|
+
# @return the values of the current node, nil if there are none.
|
261
|
+
def values
|
262
|
+
if @element
|
263
|
+
values = @element.values
|
264
|
+
values.empty? ? nil : values.clone
|
265
|
+
else
|
266
|
+
@value
|
267
|
+
end
|
268
|
+
end
|
269
|
+
alias_method :value, :values
|
270
|
+
|
271
|
+
def values?
|
272
|
+
if @element
|
273
|
+
!@element.values.empty?
|
274
|
+
else
|
275
|
+
!@value.nil?
|
276
|
+
end
|
277
|
+
end
|
278
|
+
alias_method :value?, :values?
|
279
|
+
|
280
|
+
def self.from_io(io)
|
281
|
+
self.new(io)
|
282
|
+
end
|
283
|
+
|
284
|
+
def self.from_memory(s)
|
285
|
+
self.new(StringIO.new(s))
|
286
|
+
end
|
287
|
+
|
288
|
+
# Enumerates all the parsed nodes and calls the given block.
|
289
|
+
#
|
290
|
+
# @yield [Reader] the current node
|
291
|
+
#
|
292
|
+
# @example
|
293
|
+
# open("sample.sdl") do |io|
|
294
|
+
# SDL4R::Reader.from_io(io).each do |node]
|
295
|
+
# puts node.node_type
|
296
|
+
# end
|
297
|
+
# end
|
298
|
+
#
|
299
|
+
def each(&block)
|
300
|
+
while node = self.read
|
301
|
+
block.call(node)
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
# Reads the next node in the SDL structure.
|
306
|
+
#
|
307
|
+
# @example
|
308
|
+
# open("sample.sdl") do |io|
|
309
|
+
# reader = SDL4R::Reader.from_io(io)
|
310
|
+
# while node = reader.read
|
311
|
+
# puts node.node_type
|
312
|
+
# end
|
313
|
+
# end
|
314
|
+
#
|
315
|
+
# @return [Reader] returns a Reader if a new node has been reached or +nil+ if the end of file
|
316
|
+
# has been reached.
|
317
|
+
def read
|
318
|
+
clear_node
|
319
|
+
|
320
|
+
node = nil
|
321
|
+
|
322
|
+
while @tokenizer.read
|
323
|
+
handler = @handler_set[@tokenizer.token_type]
|
324
|
+
unless handler
|
325
|
+
raise_unexpected_token
|
326
|
+
end
|
327
|
+
if handler.call(self)
|
328
|
+
node = self if @node_type # otherwise, we reached the end of the file
|
329
|
+
break
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
node
|
334
|
+
end
|
335
|
+
|
336
|
+
# @private
|
337
|
+
def raise_unexpected_token
|
338
|
+
@tokenizer.raise_parse_error(
|
339
|
+
"unexpected token #{@tokenizer.token_type} #{@tokenizer.token.inspect}",
|
340
|
+
@tokenizer.token_line_no,
|
341
|
+
@tokenizer.token_pos)
|
342
|
+
end
|
343
|
+
|
344
|
+
def set_mode(mode)
|
345
|
+
handler_set = @@handler_sets[mode]
|
346
|
+
raise ArgumentError, "unknown mode #{mode.to_s}" unless handler_set
|
347
|
+
@mode = mode
|
348
|
+
@handler_set = handler_set
|
349
|
+
end
|
350
|
+
protected :set_mode
|
351
|
+
|
352
|
+
# Creates and returns the object representing a datetime (calls SDL4R#new_time by default).
|
353
|
+
# Can be overriden.
|
354
|
+
#
|
355
|
+
# def new_time(year, month, day, hour, min, sec, msec, timezone_code)
|
356
|
+
# Time.utc(year, month, day, hour, min, sec, msec, timezone_code)
|
357
|
+
# end
|
358
|
+
#
|
359
|
+
def new_time(year, month, day, hour, min, sec, msec, timezone_code)
|
360
|
+
SDL4R::new_time(year, month, day, hour, min, sec, msec, timezone_code)
|
361
|
+
end
|
362
|
+
|
363
|
+
# @private
|
364
|
+
def on_simple_comment # :nodoc:
|
365
|
+
# @node_type = TYPE_COMMENT
|
366
|
+
false
|
367
|
+
end
|
368
|
+
|
369
|
+
# @private
|
370
|
+
def on_eof # :nodoc:
|
371
|
+
@node_type = nil
|
372
|
+
true
|
373
|
+
end
|
374
|
+
|
375
|
+
# @private
|
376
|
+
def on_multiline_comment # :nodoc:
|
377
|
+
# @node_type = TYPE_COMMENT
|
378
|
+
@value = @tokenizer.token
|
379
|
+
|
380
|
+
while @tokenizer.read
|
381
|
+
case @tokenizer.token_type
|
382
|
+
when :EOL
|
383
|
+
@value << ?\n
|
384
|
+
when :MULTILINE_COMMENT_PART
|
385
|
+
@value << @tokenizer.token
|
386
|
+
when :MULTILINE_COMMENT_END
|
387
|
+
@value << @tokenizer.token
|
388
|
+
break
|
389
|
+
else
|
390
|
+
raise_unexpected_token
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
false
|
395
|
+
end
|
396
|
+
|
397
|
+
# @private
|
398
|
+
def on_tag_start # :nodoc:
|
399
|
+
read_name
|
400
|
+
set_mode :tag_values
|
401
|
+
@element = Element.new @prefix, @name
|
402
|
+
|
403
|
+
false
|
404
|
+
end
|
405
|
+
|
406
|
+
# @private
|
407
|
+
def on_self_closing_tag # :nodoc:
|
408
|
+
@node_type = TYPE_ELEMENT
|
409
|
+
@prefix = @element.prefix
|
410
|
+
@name = @element.name
|
411
|
+
@element.self_closing = true
|
412
|
+
set_mode(@depth <= 1 ? :top : :tag_body)
|
413
|
+
end
|
414
|
+
|
415
|
+
# @private
|
416
|
+
def on_attribute # :nodoc:
|
417
|
+
read_name
|
418
|
+
read_equal
|
419
|
+
read_value
|
420
|
+
set_mode :tag_attributes
|
421
|
+
@element.add_attribute(@prefix, @name, @value)
|
422
|
+
|
423
|
+
false
|
424
|
+
end
|
425
|
+
|
426
|
+
# @private
|
427
|
+
def on_value # :nodoc:
|
428
|
+
read_value
|
429
|
+
set_mode :tag_values
|
430
|
+
@element.add_value(@value)
|
431
|
+
|
432
|
+
false
|
433
|
+
end
|
434
|
+
|
435
|
+
# Should only be called from :top or :tag_body modes.
|
436
|
+
# @private
|
437
|
+
def on_anonymous_value # :nodoc:
|
438
|
+
set_mode :tag_values
|
439
|
+
@element = Element.new '', SDL4R::ANONYMOUS_TAG_NAME
|
440
|
+
|
441
|
+
on_value
|
442
|
+
end
|
443
|
+
|
444
|
+
# @private
|
445
|
+
def on_tag_body_start # :nodoc:
|
446
|
+
@node_type = TYPE_ELEMENT
|
447
|
+
@prefix = @element.prefix
|
448
|
+
@name = @element.name
|
449
|
+
@depth += 1
|
450
|
+
set_mode :tag_body
|
451
|
+
|
452
|
+
true
|
453
|
+
end
|
454
|
+
|
455
|
+
# @private
|
456
|
+
def on_tag_body_end # :nodoc:
|
457
|
+
if @depth <= 1
|
458
|
+
raise "unexpected end of tag"
|
459
|
+
end
|
460
|
+
|
461
|
+
clear_node
|
462
|
+
@node_type = TYPE_END_ELEMENT
|
463
|
+
@depth -= 1
|
464
|
+
set_mode(@depth <= 1 ? :top : :tag_body)
|
465
|
+
|
466
|
+
true
|
467
|
+
end
|
468
|
+
|
469
|
+
# @private
|
470
|
+
def parse_double_quote_string(s)
|
471
|
+
return s if s.empty?
|
472
|
+
|
473
|
+
string = ""
|
474
|
+
escaped = false
|
475
|
+
|
476
|
+
s.each_char do |c|
|
477
|
+
if escaped
|
478
|
+
escaped = false
|
479
|
+
|
480
|
+
case c
|
481
|
+
when "\\", "\""
|
482
|
+
string << c
|
483
|
+
when "n"
|
484
|
+
string << ?\n
|
485
|
+
when "r"
|
486
|
+
string << ?\r
|
487
|
+
when "t"
|
488
|
+
string << ?\t
|
489
|
+
else
|
490
|
+
@tokenizer.raise_parse_error("Illegal escape character in string literal: '#{c}'.")
|
491
|
+
end
|
492
|
+
|
493
|
+
elsif c == "\\"
|
494
|
+
escaped = true
|
495
|
+
|
496
|
+
else
|
497
|
+
string << c
|
498
|
+
end
|
499
|
+
end
|
500
|
+
|
501
|
+
@tokenizer.raise_parse_error("orphan backslash") if escaped
|
502
|
+
|
503
|
+
string
|
504
|
+
end
|
505
|
+
|
506
|
+
# @private
|
507
|
+
def parse_multiline_string(first_string, part_token_type, end_token_type)
|
508
|
+
string = ""
|
509
|
+
string << first_string
|
510
|
+
|
511
|
+
loop do
|
512
|
+
case @tokenizer.read
|
513
|
+
when :EOL
|
514
|
+
# skip
|
515
|
+
when part_token_type
|
516
|
+
string << @tokenizer.token
|
517
|
+
when end_token_type
|
518
|
+
string << @tokenizer.token
|
519
|
+
break
|
520
|
+
else
|
521
|
+
raise_unexpected_token
|
522
|
+
end
|
523
|
+
end
|
524
|
+
|
525
|
+
string
|
526
|
+
end
|
527
|
+
private :parse_multiline_string
|
528
|
+
|
529
|
+
# @private
|
530
|
+
def parse_multiline_backquote_string(s)
|
531
|
+
parse_multiline_string s, :MULTILINE_BACKQUOTE_STRING_PART, :MULTILINE_BACKQUOTE_STRING_END
|
532
|
+
end
|
533
|
+
|
534
|
+
# @private
|
535
|
+
def parse_multiline_double_quote_string(s)
|
536
|
+
parse_double_quote_string(
|
537
|
+
parse_multiline_string(
|
538
|
+
s, :MULTILINE_DOUBLE_QUOTE_STRING_PART, :MULTILINE_DOUBLE_QUOTE_STRING_END))
|
539
|
+
end
|
540
|
+
|
541
|
+
# @private
|
542
|
+
def parse_multiline_binary(s)
|
543
|
+
literal = parse_multiline_string s, :MULTILINE_BINARY_PART, :MULTILINE_BINARY_END
|
544
|
+
return SdlBinary.decode64(literal)
|
545
|
+
end
|
546
|
+
|
547
|
+
# @private
|
548
|
+
def parse_character(s)
|
549
|
+
case s
|
550
|
+
when /\A.\Z/
|
551
|
+
s
|
552
|
+
when "\\\\"
|
553
|
+
"\\"
|
554
|
+
when "\\'"
|
555
|
+
"'"
|
556
|
+
when "\\n"
|
557
|
+
"\n"
|
558
|
+
when "\\r"
|
559
|
+
"\r"
|
560
|
+
when "\\t"
|
561
|
+
"\t"
|
562
|
+
else
|
563
|
+
raise "illegal character literal #{s.inspect}"
|
564
|
+
end
|
565
|
+
end
|
566
|
+
|
567
|
+
# @private
|
568
|
+
def parse_integer(s)
|
569
|
+
if s =~ /\A([^L]+)L\Z/i
|
570
|
+
return Integer($1)
|
571
|
+
else
|
572
|
+
return Integer(s)
|
573
|
+
end
|
574
|
+
end
|
575
|
+
|
576
|
+
# @private
|
577
|
+
def parse_float(s)
|
578
|
+
if s =~ /\A([^BDF]+)BD\Z/i
|
579
|
+
return BigDecimal($1)
|
580
|
+
elsif s =~ /\A([^BDF]+)[FD]\Z/i
|
581
|
+
return Float($1) rescue @tokenizer.raise_parse_error("not a float '#{$1}'")
|
582
|
+
else
|
583
|
+
return Float(s) rescue @tokenizer.raise_parse_error("not a float '#{s}'")
|
584
|
+
end
|
585
|
+
end
|
586
|
+
|
587
|
+
# Parses the +literal+ into a returned Date object.
|
588
|
+
#
|
589
|
+
# Raises an ArgumentError if +literal+ has a bad format.
|
590
|
+
#
|
591
|
+
# @private
|
592
|
+
def parse_date(literal)
|
593
|
+
# here, we're being stricter than strptime() alone as we forbid trailing chars (also faster)
|
594
|
+
if literal =~ /\A(-?\d+)\/(\d+)\/(\d+)\Z/
|
595
|
+
date_year = $1.to_i
|
596
|
+
date_month = $2.to_i
|
597
|
+
date_day = $3.to_i
|
598
|
+
|
599
|
+
skip_whitespaces(false)
|
600
|
+
|
601
|
+
# Check whether the next tag is the time part
|
602
|
+
if @tokenizer.token_type == :TIME_OR_TIMESPAN
|
603
|
+
# Is it a time or timespan?
|
604
|
+
day, hour, min, sec, msec, zone =
|
605
|
+
parse_time_span_and_time_zone(@tokenizer.token, true, true)
|
606
|
+
|
607
|
+
if day
|
608
|
+
@tokenizer.unread
|
609
|
+
return Date.civil(date_year, date_month, date_day)
|
610
|
+
else
|
611
|
+
return new_time(date_year, date_month, date_day, hour, min, sec, msec, zone)
|
612
|
+
end
|
613
|
+
|
614
|
+
else
|
615
|
+
@tokenizer.unread
|
616
|
+
return Date.civil(date_year, date_month, date_day)
|
617
|
+
end
|
618
|
+
|
619
|
+
else
|
620
|
+
raise ArgumentError, "Malformed Date <#{literal}>"
|
621
|
+
end
|
622
|
+
end
|
623
|
+
|
624
|
+
# Parses +literal+ (String) into the corresponding SDLTimeSpan, which is then
|
625
|
+
# returned.
|
626
|
+
#
|
627
|
+
# Raises an ArgumentError if the literal is not a correct timespan literal.
|
628
|
+
#
|
629
|
+
# @private
|
630
|
+
def parse_time_span(literal)
|
631
|
+
days, hours, minutes, seconds, milliseconds, zone_code =
|
632
|
+
parse_time_span_and_time_zone(literal, true, false)
|
633
|
+
|
634
|
+
if zone_code
|
635
|
+
@tokenizer.raise_parse_error("got a time when expecting a timespan: \"#{literal}\"")
|
636
|
+
end
|
637
|
+
|
638
|
+
return SDL4R::SdlTimeSpan.new(days || 0, hours, minutes, seconds, milliseconds)
|
639
|
+
end
|
640
|
+
|
641
|
+
private
|
642
|
+
|
643
|
+
# Parses the given literal into a returned array
|
644
|
+
# [days, hours, minutes, seconds, milliseconds, zone_code].
|
645
|
+
# 'days', 'hours', 'minutes', 'seconds', 'milliseconds' are integers.
|
646
|
+
# 'days' is +nil+ if not specified in +literal+.
|
647
|
+
# 'seconds' and 'milliseconds' are equal to 0 if they're not specified in +literal+.
|
648
|
+
# 'zone_code' (string) is equal to nil if not specified.
|
649
|
+
#
|
650
|
+
# +allowDays+ indicates whether the specification of days is allowed
|
651
|
+
# in +literal+
|
652
|
+
# +allowTimeZone+ indicates whether the specification of the timeZone is
|
653
|
+
# allowed in +literal+
|
654
|
+
#
|
655
|
+
# All components are returned disregarding the values of +allowDays+ and
|
656
|
+
# +allowTimeZone+.
|
657
|
+
#
|
658
|
+
# Raises an ArgumentError if +literal+ has a bad format.
|
659
|
+
def parse_time_span_and_time_zone(literal, allowDays, allowTimeZone)
|
660
|
+
overall_sign = (literal =~ /^-/)? -1 : +1
|
661
|
+
|
662
|
+
if literal =~ /\A(([+\-]?\d+)d:)/
|
663
|
+
if allowDays
|
664
|
+
days = Integer($2)
|
665
|
+
time_part = literal[($1.length)..-1]
|
666
|
+
else
|
667
|
+
# detected a day specification in a pure time literal
|
668
|
+
raise ArgumentError, "unexpected day specification in #{literal}"
|
669
|
+
end
|
670
|
+
else
|
671
|
+
days = nil
|
672
|
+
time_part = literal
|
673
|
+
end
|
674
|
+
|
675
|
+
# We have to parse the string ourselves because AFAIK :
|
676
|
+
# - strptime() can't parse milliseconds
|
677
|
+
# - strptime() can't parse the time zone custom offset (CET+02:30)
|
678
|
+
# - strptime() accepts trailing chars
|
679
|
+
# (e.g. "12:24-xyz@" ==> "xyz@" is obviously wrong but strptime()
|
680
|
+
# won't mind)
|
681
|
+
if /\A([+-]?\d+):(\d+)(?::(\d+)(?:\.(\d+))?)?
|
682
|
+
(?:-([a-zA-Z0-9\/_]+(?:[+\-]\d+(?::\d+)?)?))?\Z/ix =~ time_part
|
683
|
+
hours = $1.to_i
|
684
|
+
minutes = $2.to_i
|
685
|
+
seconds = $3 ? $3.to_i : 0
|
686
|
+
milliseconds =
|
687
|
+
if $4
|
688
|
+
millisecond_part = ($4)? $4.ljust(3, '0') : nil
|
689
|
+
millisecond_part.to_i
|
690
|
+
else
|
691
|
+
0
|
692
|
+
end
|
693
|
+
|
694
|
+
if $5 and not allowTimeZone
|
695
|
+
raise ArgumentError, "unexpected time zone specification in #{literal}"
|
696
|
+
end
|
697
|
+
|
698
|
+
zone_code = $5 # might be nil
|
699
|
+
|
700
|
+
if not allowDays and $1 =~ /\A[+-]/
|
701
|
+
# unexpected timeSpan syntax
|
702
|
+
raise ArgumentError, "unexpected sign on hours : #{literal}"
|
703
|
+
end
|
704
|
+
|
705
|
+
# take the sign into account
|
706
|
+
if overall_sign == -1
|
707
|
+
hours = -hours if days # otherwise the sign is already applied to the hours
|
708
|
+
minutes = -minutes
|
709
|
+
seconds = -seconds
|
710
|
+
milliseconds = -milliseconds
|
711
|
+
end
|
712
|
+
|
713
|
+
return [ days, hours, minutes, seconds, milliseconds, zone_code ]
|
714
|
+
|
715
|
+
else
|
716
|
+
raise ArgumentError, "bad time component : #{literal}"
|
717
|
+
end
|
718
|
+
end
|
719
|
+
|
720
|
+
def skip_whitespaces(allow_line_continuations = false)
|
721
|
+
while token_type = @tokenizer.read
|
722
|
+
case token_type
|
723
|
+
when :WHITESPACE
|
724
|
+
# skip
|
725
|
+
when :LINE_CONTINUATION
|
726
|
+
break unless allow_line_continuations
|
727
|
+
else
|
728
|
+
break
|
729
|
+
end
|
730
|
+
end
|
731
|
+
end
|
732
|
+
|
733
|
+
# @private
|
734
|
+
def read_equal
|
735
|
+
skip_whitespaces(false)
|
736
|
+
unless @tokenizer.token_type == :EQUAL
|
737
|
+
raise_unexpected_token
|
738
|
+
end
|
739
|
+
skip_whitespaces(true)
|
740
|
+
end
|
741
|
+
|
742
|
+
# @private
|
743
|
+
def read_name
|
744
|
+
@name = @tokenizer.token
|
745
|
+
|
746
|
+
if @tokenizer.read == :COLON
|
747
|
+
# Namespace + Name (except syntax error)
|
748
|
+
if @tokenizer.read == :IDENTIFIER
|
749
|
+
@prefix = @name
|
750
|
+
@name = @tokenizer.token
|
751
|
+
|
752
|
+
else
|
753
|
+
raise_unexpected_token
|
754
|
+
end
|
755
|
+
|
756
|
+
else # Just a Name, it seems
|
757
|
+
@tokenizer.unread
|
758
|
+
@prefix = ''
|
759
|
+
end
|
760
|
+
|
761
|
+
@name
|
762
|
+
end
|
763
|
+
|
764
|
+
# @private
|
765
|
+
def read_value
|
766
|
+
handler = @@value_handlers[@tokenizer.token_type]
|
767
|
+
raise_unexpected_token unless handler
|
768
|
+
@value = handler.call(@tokenizer.token, self)
|
769
|
+
@value
|
770
|
+
end
|
771
|
+
end
|
772
|
+
end
|