sdl4r 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README +3 -0
- data/Rakefile +45 -0
- data/TODO.txt +117 -0
- data/lib/scratchpad.rb +49 -0
- data/lib/sdl4r/parser.rb +678 -0
- data/lib/sdl4r/reader.rb +171 -0
- data/lib/sdl4r/sdl.rb +242 -0
- data/lib/sdl4r/sdl_binary.rb +78 -0
- data/lib/sdl4r/sdl_parse_error.rb +44 -0
- data/lib/sdl4r/sdl_time_span.rb +301 -0
- data/lib/sdl4r/tag.rb +949 -0
- data/lib/sdl4r/token.rb +129 -0
- data/lib/sdl4r/tokenizer.rb +501 -0
- data/test/sdl4r/parser_test.rb +295 -0
- data/test/sdl4r/test.rb +541 -0
- data/test/sdl4r/test_basic_types.sdl +138 -0
- data/test/sdl4r/test_structures.sdl +180 -0
- metadata +81 -0
@@ -0,0 +1,44 @@
|
|
1
|
+
# Simple Declarative Language (SDL) for Ruby
|
2
|
+
# Copyright 2005 Ikayzo, inc.
|
3
|
+
#
|
4
|
+
# This program is free software. You can distribute or modify it under the
|
5
|
+
# terms of the GNU Lesser General Public License version 2.1 as published by
|
6
|
+
# the Free Software Foundation.
|
7
|
+
#
|
8
|
+
# This program is distributed AS IS and WITHOUT WARRANTY. OF ANY KIND,
|
9
|
+
# INCLUDING MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
|
10
|
+
# See the GNU Lesser General Public License for more details.
|
11
|
+
#
|
12
|
+
# You should have received a copy of the GNU Lesser General Public License
|
13
|
+
# along with this program; if not, contact the Free Software Foundation, Inc.,
|
14
|
+
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
15
|
+
|
16
|
+
|
17
|
+
module SDL4R
|
18
|
+
#
|
19
|
+
# An exception describing a problem with an SDL document's structure
|
20
|
+
#
|
21
|
+
class SdlParseError < StandardError
|
22
|
+
|
23
|
+
#
|
24
|
+
# Note: Line and positioning numbering start with 1 rather than 0 to be
|
25
|
+
# consistent with most editors.
|
26
|
+
#
|
27
|
+
# +description+ A description of the problem.
|
28
|
+
# +lineNo+ The line on which the error occured or -1 for unknown
|
29
|
+
# +position+ The position (within the line) where the error occured or -1 for unknown
|
30
|
+
#
|
31
|
+
def initialize(description, line_no, position, line = nil)
|
32
|
+
super(
|
33
|
+
"#{description} Line " + ((line_no.nil? or line_no < 0)? "unknown" : line_no.to_s) +
|
34
|
+
", Position " + ((position.nil? or position < 0)? "unknown" : position.to_s) + "\n" +
|
35
|
+
(line ? line + (position ? " " * (position - 1) : "") + "^" : ""))
|
36
|
+
|
37
|
+
@lineNo = line_no
|
38
|
+
@position = position
|
39
|
+
end
|
40
|
+
|
41
|
+
attr_reader :line
|
42
|
+
attr_reader :position
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,301 @@
|
|
1
|
+
# Simple Declarative Language (SDL) for Ruby
|
2
|
+
# Copyright 2005 Ikayzo, inc.
|
3
|
+
#
|
4
|
+
# This program is free software. You can distribute or modify it under the
|
5
|
+
# terms of the GNU Lesser General Public License version 2.1 as published by
|
6
|
+
# the Free Software Foundation.
|
7
|
+
#
|
8
|
+
# This program is distributed AS IS and WITHOUT WARRANTY. OF ANY KIND,
|
9
|
+
# INCLUDING MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
|
10
|
+
# See the GNU Lesser General Public License for more details.
|
11
|
+
#
|
12
|
+
# You should have received a copy of the GNU Lesser General Public License
|
13
|
+
# along with this program; if not, contact the Free Software Foundation, Inc.,
|
14
|
+
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
15
|
+
|
16
|
+
|
17
|
+
module SDL4R
|
18
|
+
|
19
|
+
# Represents a period of time (duration) as opposed to a particular
|
20
|
+
# moment in time (which would be represented using a Date, DateTime or Time
|
21
|
+
# instance).
|
22
|
+
#
|
23
|
+
class SdlTimeSpan
|
24
|
+
include Comparable
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
MILLISECONDS_IN_SECOND = 1000
|
29
|
+
MILLISECONDS_IN_MINUTE = 60 * MILLISECONDS_IN_SECOND
|
30
|
+
MILLISECONDS_IN_HOUR = 60 * MILLISECONDS_IN_MINUTE
|
31
|
+
MILLISECONDS_IN_DAY = 24 * MILLISECONDS_IN_HOUR
|
32
|
+
|
33
|
+
|
34
|
+
# Initializes an SdlTimeSpan using the total number of milliseconds in the
|
35
|
+
# span.
|
36
|
+
#
|
37
|
+
def initialize_total_milliseconds(total_milliseconds)
|
38
|
+
@totalMilliseconds = total_milliseconds
|
39
|
+
end
|
40
|
+
|
41
|
+
# Initializes an SdlTimeSpan defined by its day, hour, minute and
|
42
|
+
# millisecond parts.
|
43
|
+
# Note: if the timespan is negative all components should be negative.
|
44
|
+
#
|
45
|
+
def initialize_days_hours_minutes(days, hours, minutes, seconds = 0, milliseconds = 0)
|
46
|
+
if seconds.is_a?(Rational)
|
47
|
+
s = seconds.truncate
|
48
|
+
milliseconds = milliseconds + ((seconds - s) * 1000).round
|
49
|
+
seconds = s
|
50
|
+
end
|
51
|
+
|
52
|
+
@totalMilliseconds =
|
53
|
+
days * MILLISECONDS_IN_DAY +
|
54
|
+
hours * MILLISECONDS_IN_HOUR +
|
55
|
+
minutes * MILLISECONDS_IN_MINUTE +
|
56
|
+
seconds * MILLISECONDS_IN_SECOND +
|
57
|
+
milliseconds
|
58
|
+
end
|
59
|
+
|
60
|
+
public
|
61
|
+
|
62
|
+
# Create an SdlTimeSpan. Note: if the timespan is negative all
|
63
|
+
# components should be negative.
|
64
|
+
#
|
65
|
+
# SdlTimeSpan.new(days, hours, minutes, seconds = 0, milliseconds = 0)
|
66
|
+
#
|
67
|
+
# or
|
68
|
+
#
|
69
|
+
# SdlTimeSpan.new(totalMilliseconds)
|
70
|
+
#
|
71
|
+
def initialize(*args)
|
72
|
+
if args.length == 1
|
73
|
+
initialize_total_milliseconds(args[0])
|
74
|
+
else
|
75
|
+
initialize_days_hours_minutes(*args)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Returns the sign (-1 or +1) of this SdlTimeSpan.
|
80
|
+
#
|
81
|
+
def sign
|
82
|
+
@totalMilliseconds <=> 0
|
83
|
+
end
|
84
|
+
|
85
|
+
# The days component.
|
86
|
+
#
|
87
|
+
def days
|
88
|
+
sign * (@totalMilliseconds.abs / MILLISECONDS_IN_DAY)
|
89
|
+
end
|
90
|
+
|
91
|
+
# The hours component.
|
92
|
+
#
|
93
|
+
def hours
|
94
|
+
return sign * ((@totalMilliseconds - (days * MILLISECONDS_IN_DAY)).abs / MILLISECONDS_IN_HOUR)
|
95
|
+
end
|
96
|
+
|
97
|
+
# The minutes component.
|
98
|
+
#
|
99
|
+
def minutes
|
100
|
+
return sign *
|
101
|
+
((@totalMilliseconds - (days * MILLISECONDS_IN_DAY) - (hours * MILLISECONDS_IN_HOUR)).abs /
|
102
|
+
MILLISECONDS_IN_MINUTE)
|
103
|
+
end
|
104
|
+
|
105
|
+
# The seconds component.
|
106
|
+
#
|
107
|
+
def seconds
|
108
|
+
return sign *
|
109
|
+
((@totalMilliseconds - (days * MILLISECONDS_IN_DAY) - (hours * MILLISECONDS_IN_HOUR) -
|
110
|
+
(minutes * MILLISECONDS_IN_MINUTE)).abs /
|
111
|
+
MILLISECONDS_IN_SECOND)
|
112
|
+
end
|
113
|
+
|
114
|
+
# The milliseconds component.
|
115
|
+
#
|
116
|
+
def milliseconds
|
117
|
+
return @totalMilliseconds -
|
118
|
+
(days * MILLISECONDS_IN_DAY) -
|
119
|
+
(hours * MILLISECONDS_IN_HOUR) -
|
120
|
+
(minutes * MILLISECONDS_IN_MINUTE) -
|
121
|
+
(seconds * MILLISECONDS_IN_SECOND)
|
122
|
+
end
|
123
|
+
|
124
|
+
# Get the total number of hours in this time span. For example, if
|
125
|
+
# this time span represents two days, this method will return 48.
|
126
|
+
#
|
127
|
+
# @return This timespan in hours
|
128
|
+
#
|
129
|
+
def total_hours
|
130
|
+
return sign * (@totalMilliseconds.abs / MILLISECONDS_IN_HOUR)
|
131
|
+
end
|
132
|
+
|
133
|
+
#
|
134
|
+
# Get the total number of minutes in this time span. For example, if
|
135
|
+
# this time span represents two hours, this method will return 120.
|
136
|
+
#
|
137
|
+
# @return This timespan in minutes
|
138
|
+
#
|
139
|
+
def total_minutes
|
140
|
+
return sign * (@totalMilliseconds.abs / MILLISECONDS_IN_MINUTE)
|
141
|
+
end
|
142
|
+
|
143
|
+
#
|
144
|
+
# Get the total number of seconds in this time span. For example, if
|
145
|
+
# this time span represents three minutes, this method will return 180.
|
146
|
+
#
|
147
|
+
# @return This timespan in seconds
|
148
|
+
#
|
149
|
+
def total_seconds
|
150
|
+
return sign * (@totalMilliseconds.abs / MILLISECONDS_IN_SECOND)
|
151
|
+
end
|
152
|
+
|
153
|
+
#
|
154
|
+
# Get the total number of milliseconds in this time span. For example, if
|
155
|
+
# this time span represents 4 seconds, this method will return 4000.
|
156
|
+
#
|
157
|
+
# @return This timespan in milliseconds
|
158
|
+
#
|
159
|
+
def total_milliseconds
|
160
|
+
return @totalMilliseconds
|
161
|
+
end
|
162
|
+
|
163
|
+
#
|
164
|
+
# Returns an new SdlTimeSpan instance that is the opposite of this
|
165
|
+
# instance
|
166
|
+
#
|
167
|
+
# @return An instance representing the inverse of this instance
|
168
|
+
#
|
169
|
+
def negate
|
170
|
+
SdlTimeSpan.new(-@totalMilliseconds)
|
171
|
+
end
|
172
|
+
|
173
|
+
#
|
174
|
+
# Return a new instance with the days adjusted by the given amount.
|
175
|
+
# Positive numbers add days. Negative numbers remove days.
|
176
|
+
#
|
177
|
+
# @param days The adjustment (days to add or subtract)
|
178
|
+
# @return A new instance with the given adjustment.
|
179
|
+
#
|
180
|
+
def roll_days(days)
|
181
|
+
SdlTimeSpan.new(@totalMilliseconds + (days * MILLISECONDS_IN_DAY))
|
182
|
+
end
|
183
|
+
|
184
|
+
#
|
185
|
+
# Return a new instance with the hours adjusted by the given amount.
|
186
|
+
# Positive numbers add hours. Negative numbers remove hours.
|
187
|
+
#
|
188
|
+
# @param hours The adjustment (hours to add or subtract)
|
189
|
+
# @return A new instance with the given adjustment.
|
190
|
+
#
|
191
|
+
def roll_hours(hours)
|
192
|
+
SdlTimeSpan.new(@totalMilliseconds + (hours * MILLISECONDS_IN_HOUR))
|
193
|
+
end
|
194
|
+
|
195
|
+
#
|
196
|
+
# Return a new instance with the minutes adjusted by the given amount.
|
197
|
+
# Positive numbers add minutes. Negative numbers remove minutes.
|
198
|
+
#
|
199
|
+
# @param minutes The adjustment (minutes to add or subtract)
|
200
|
+
# @return A new instance with the given adjustment.
|
201
|
+
#
|
202
|
+
def roll_minutes(minutes)
|
203
|
+
SdlTimeSpan.new(@totalMilliseconds + (minutes * MILLISECONDS_IN_MINUTE))
|
204
|
+
end
|
205
|
+
|
206
|
+
#
|
207
|
+
# Return a new instance with the seconds adjusted by the given amount.
|
208
|
+
# Positive numbers add seconds. Negative numbers remove seconds.
|
209
|
+
#
|
210
|
+
# @param seconds The adjustment (seconds to add or subtract)
|
211
|
+
# @return A new instance with the given adjustment.
|
212
|
+
#
|
213
|
+
def roll_seconds(seconds)
|
214
|
+
SdlTimeSpan.new(@totalMilliseconds + (seconds * MILLISECONDS_IN_SECOND))
|
215
|
+
end
|
216
|
+
|
217
|
+
#
|
218
|
+
# Return a new instance with the milliseconds adjusted by the given amount.
|
219
|
+
# Positive numbers add milliseconds. Negative numbers remove milliseconds.
|
220
|
+
#
|
221
|
+
# @param milliseconds The adjustment (milliseconds to add or subtract)
|
222
|
+
# @return A new instance with the given adjustment.
|
223
|
+
#
|
224
|
+
def roll_milliseconds(milliseconds)
|
225
|
+
SdlTimeSpan.new(@totalMilliseconds + milliseconds)
|
226
|
+
end
|
227
|
+
|
228
|
+
#
|
229
|
+
# A hashcode based on the canonical string representation.
|
230
|
+
#
|
231
|
+
# @return A hashcode for this time span
|
232
|
+
#
|
233
|
+
def hash
|
234
|
+
to_s.hash
|
235
|
+
end
|
236
|
+
|
237
|
+
# Tests for equivalence.
|
238
|
+
#
|
239
|
+
# @return true If the given object is equivalent
|
240
|
+
#
|
241
|
+
def eql?(other)
|
242
|
+
other.is_a?(SdlTimeSpan) and @totalMilliseconds == other.total_milliseconds
|
243
|
+
end
|
244
|
+
|
245
|
+
# define '==' as 'eql?'
|
246
|
+
alias_method :==, :eql?
|
247
|
+
|
248
|
+
def <=>(other)
|
249
|
+
@totalMilliseconds <=> other.total_milliseconds
|
250
|
+
end
|
251
|
+
|
252
|
+
# <p>Returns an SDL representation of this time span using the format:<p>
|
253
|
+
#
|
254
|
+
# <pre>
|
255
|
+
# (days:)hours:minutes:seconds(.milliseconds)
|
256
|
+
# </pre>
|
257
|
+
#
|
258
|
+
# <p>(parenthesis indicate optional components)
|
259
|
+
#
|
260
|
+
# <p>The days and milliseconds components will not be included if they
|
261
|
+
# are set to 0. Days must be suffixed with "d" for clarity.</p>
|
262
|
+
#
|
263
|
+
# <p>Hours, minutes, and seconds will be zero paded to two characters.</p>
|
264
|
+
#
|
265
|
+
# <p>Examples:</p>
|
266
|
+
# <pre>
|
267
|
+
# 23:13:00 (12 hours and 13 minutes)
|
268
|
+
# 24d:12:13:09.234 (24 days, 12 hours, 13 minutes, 9 seconds,
|
269
|
+
# 234 milliseconds)
|
270
|
+
# </pre>
|
271
|
+
#
|
272
|
+
# @return an SDL representation of this time span
|
273
|
+
#
|
274
|
+
def to_s
|
275
|
+
_days = days
|
276
|
+
_milliseconds = milliseconds
|
277
|
+
|
278
|
+
s = nil
|
279
|
+
if _days == 0
|
280
|
+
if _milliseconds == 0
|
281
|
+
s = sprintf("%d:%02d:%02d", hours, minutes.abs, seconds.abs)
|
282
|
+
else
|
283
|
+
s = sprintf("%d:%02d:%02d.%03d", hours, minutes.abs, seconds.abs, _milliseconds.abs)
|
284
|
+
end
|
285
|
+
else
|
286
|
+
if _milliseconds == 0
|
287
|
+
s = sprintf("%dd:%02d:%02d:%02d", _days, hours.abs, minutes.abs, seconds.abs)
|
288
|
+
else
|
289
|
+
s = sprintf(
|
290
|
+
"%dd:%02d:%02d:%02d.%03d",
|
291
|
+
_days,
|
292
|
+
hours.abs,
|
293
|
+
minutes.abs,
|
294
|
+
seconds.abs,
|
295
|
+
_milliseconds.abs)
|
296
|
+
end
|
297
|
+
end
|
298
|
+
return s
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
data/lib/sdl4r/tag.rb
ADDED
@@ -0,0 +1,949 @@
|
|
1
|
+
# Simple Declarative Language (SDL) for Ruby
|
2
|
+
# Copyright 2005 Ikayzo, inc.
|
3
|
+
#
|
4
|
+
# This program is free software. You can distribute or modify it under the
|
5
|
+
# terms of the GNU Lesser General Public License version 2.1 as published by
|
6
|
+
# the Free Software Foundation.
|
7
|
+
#
|
8
|
+
# This program is distributed AS IS and WITHOUT WARRANTY. OF ANY KIND,
|
9
|
+
# INCLUDING MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
|
10
|
+
# See the GNU Lesser General Public License for more details.
|
11
|
+
#
|
12
|
+
# You should have received a copy of the GNU Lesser General Public License
|
13
|
+
# along with this program; if not, contact the Free Software Foundation, Inc.,
|
14
|
+
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
15
|
+
|
16
|
+
|
17
|
+
module SDL4R
|
18
|
+
|
19
|
+
require 'stringio'
|
20
|
+
|
21
|
+
require File.dirname(__FILE__) + '/sdl'
|
22
|
+
require File.dirname(__FILE__) + '/parser'
|
23
|
+
|
24
|
+
# SDL (Simple Declarative Language) documents are made up of Tags. A Tag
|
25
|
+
# contains
|
26
|
+
#
|
27
|
+
# * a name (if not present, the name "content" is used)
|
28
|
+
# * a namespace (optional)
|
29
|
+
# * 0 or more values (optional)
|
30
|
+
# * 0 or more attributes (optional)
|
31
|
+
# * 0 or more children (optional)
|
32
|
+
#
|
33
|
+
# For the SDL code:
|
34
|
+
#
|
35
|
+
# size 4
|
36
|
+
# smoker false
|
37
|
+
#
|
38
|
+
#
|
39
|
+
# Assuming this code is in a file called values.sdl, the values can be read
|
40
|
+
# using the following code (ignoring exceptions):
|
41
|
+
#
|
42
|
+
# root = Tag.new("root").read(new File("values.sdl"));
|
43
|
+
# int size = root.getChild("size").intValue();
|
44
|
+
# boolean smoker = root.getChild("smoker").booleanValue();
|
45
|
+
#
|
46
|
+
# A tag is basically a data structure with a list of values, a map of
|
47
|
+
# attributes, and (if it has a body) child tags. In the example above, the
|
48
|
+
# "values.sdl" file is read into a tag called "root". It has two children
|
49
|
+
# (tags) called "size" and "smoker". Both these children have one value, no
|
50
|
+
# attributes, and no bodies.
|
51
|
+
#
|
52
|
+
# SDL is often used for simple key-value mappings. To simplify things Tag
|
53
|
+
# has the methods getValue and setValue which operate on the first element in
|
54
|
+
# the values list. Also notice SDL understands types which are determined
|
55
|
+
# using type inference.
|
56
|
+
#
|
57
|
+
# The example above used the simple format common in property files:
|
58
|
+
#
|
59
|
+
# name value
|
60
|
+
#
|
61
|
+
# The full SDL tag format is:
|
62
|
+
#
|
63
|
+
# namespace:name value_list attribute_list {
|
64
|
+
# children_tags
|
65
|
+
# }
|
66
|
+
#
|
67
|
+
# where value_list is zero or more space separated SDL literals and
|
68
|
+
# attribute_list is zero or more space separated (namespace:)key=value pairs.
|
69
|
+
# The name, namespace, and keys are SDL identifiers. Values are SDL literals.
|
70
|
+
# Namespace is optional for both tag names and attributes. Tag bodies are also
|
71
|
+
# optional. SDL identifiers begin with a unicode letter or an underscore (_)
|
72
|
+
# followed by zero or more unicode letters, numbers, underscores (_) and
|
73
|
+
# dashes (-).
|
74
|
+
#
|
75
|
+
# SDL also supports anonymous tags which are assigned the name "content".
|
76
|
+
# An annoymous tag starts with a literal and is followed by zero or more
|
77
|
+
# additional literals and zero or more attributes. The examples section below
|
78
|
+
# demonstrates the use of anonymous tags.
|
79
|
+
#
|
80
|
+
# Tags without bodies are terminated by a new line character (\n) and may be
|
81
|
+
# continue onto the next line by placing a backslash (\) at the end of the
|
82
|
+
# line. Tags may be nested to an arbitrary depth. SDL ignores all other white
|
83
|
+
# space characters between tokens. Although nested blocks are indented by
|
84
|
+
# convention, tabs have no significance in the language.
|
85
|
+
#
|
86
|
+
# There are two ways to write String literals.
|
87
|
+
#
|
88
|
+
# 1. Starting and ending with double quotes ("). Double quotes, backslash
|
89
|
+
# characters (\), and new lines (\n) within this type of String literal
|
90
|
+
# must be escaped like so:
|
91
|
+
#
|
92
|
+
# file "C:\\folder\\file.txt"
|
93
|
+
# say "I said \"something\""
|
94
|
+
#
|
95
|
+
# This type of String literal can be continued on the next line by placing a
|
96
|
+
# backslash (\) at the end of the line like so:
|
97
|
+
#
|
98
|
+
# line "this is a \
|
99
|
+
# long string of text"
|
100
|
+
#
|
101
|
+
# White space before the first character in the second line will be ignored.
|
102
|
+
#
|
103
|
+
# 2. Starting and ending with a backquote (`). This type of string literal
|
104
|
+
# can only be ended with a second backquote (`). It is not necessary (or
|
105
|
+
# possible) to escape any type of character within a backquote string
|
106
|
+
# literal. This type of literal can also span lines. All white spaces are
|
107
|
+
# preserved including new lines.
|
108
|
+
#
|
109
|
+
# Examples:
|
110
|
+
#
|
111
|
+
# file `C:\folder\file.txt`
|
112
|
+
# say `I said "something"`
|
113
|
+
# regex `\w+\.suite\(\)`
|
114
|
+
# long_line `This is
|
115
|
+
# a long line
|
116
|
+
# fee fi fo fum`
|
117
|
+
#
|
118
|
+
# Note: SDL interprets new lines in `` String literals as a single new line
|
119
|
+
# character (\n) regarless of the platform.
|
120
|
+
#
|
121
|
+
# Binary literals use base64 characters enclosed in square brackets ([]).
|
122
|
+
# The binary literal type can also span lines. White space is ignored.
|
123
|
+
#
|
124
|
+
#
|
125
|
+
# Examples:
|
126
|
+
# key [sdf789GSfsb2+3324sf2] name="my key"
|
127
|
+
# image [
|
128
|
+
# R3df789GSfsb2edfSFSDF
|
129
|
+
# uikuikk2349GSfsb2edfS
|
130
|
+
# vFSDFR3df789GSfsb2edf
|
131
|
+
# ]
|
132
|
+
# upload from="ikayzo.com" data=[
|
133
|
+
# R3df789GSfsb2edfSFSDF
|
134
|
+
# uikuikk2349GSfsb2edfS
|
135
|
+
# vFSDFR3df789GSfsb2edf
|
136
|
+
# ]
|
137
|
+
#
|
138
|
+
# SDL supports date, time span, and date/time literals. Date and Date/Time
|
139
|
+
# literals use a 24 hour clock (0-23). If a timezone is not specified, the
|
140
|
+
# default locale's timezone will be used.
|
141
|
+
#
|
142
|
+
# Examples:
|
143
|
+
#
|
144
|
+
# # create a tag called "date" with a date value of Dec 5, 2005
|
145
|
+
# date 2005/12/05
|
146
|
+
#
|
147
|
+
# # various time span literals
|
148
|
+
# hours 03:00:00
|
149
|
+
# minutes 00:12:00
|
150
|
+
# seconds 00:00:42
|
151
|
+
# short_time 00:12:32.423 # 12 minutes, 32 seconds, 423 milliseconds
|
152
|
+
# long_time 30d:15:23:04.023 # 30 days, 15 hours, 23 mins, 4 secs, 23 millis
|
153
|
+
# before -00:02:30 # 2 hours and 30 minutes ago
|
154
|
+
#
|
155
|
+
# # a date time literal
|
156
|
+
# in_japan 2005/12/05 14:12:23.345-JST
|
157
|
+
#
|
158
|
+
#
|
159
|
+
# SDL 1.0 has thirteen literal types (parenthesis indicate optional components)
|
160
|
+
#
|
161
|
+
# 1. string (unicode) - examples: "hello" or `aloha`
|
162
|
+
# 2. character (unicode) - example: '/'
|
163
|
+
# Note: \uXXXX style unicode escapes are not supported (or
|
164
|
+
# needed because sdl files are UTF8)
|
165
|
+
# 3. integer (32 bits signed) - example: 123
|
166
|
+
# 4. long integer (64 bits signed) - examples: 123L or 123l
|
167
|
+
# 5. float (32 bits signed) - examples 123.43F 123.43f
|
168
|
+
# 6. double float (64 bits signed) - example: 123.43 or 123.43d or 123.43D
|
169
|
+
# 7. decimal (128+ bits signed) - example: 123.44BD or 123.44bd
|
170
|
+
# 8. boolean - examples: true or false or on or off
|
171
|
+
# 9. date yyyy/mm/dd - example 2005/12/05
|
172
|
+
# 10. date time yyyy/mm/dd hh:mm(:ss)(.xxx)(-ZONE)
|
173
|
+
# example - 2005/12/05 05:21:23.532-JST
|
174
|
+
# notes: uses a 24 hour clock (0-23), only hours and minutes are
|
175
|
+
# mandatory
|
176
|
+
# 11. time span using the format (d:)hh:mm:ss(.xxx)
|
177
|
+
# notes: if the day component is included it must be suffixed with
|
178
|
+
# a lower case 'd'
|
179
|
+
# examples 12:14:42 (12 hours, 14 minutes, 42 seconds)
|
180
|
+
# 00:09:12 (9 minutes, 12 seconds)
|
181
|
+
# 00:00:01.023 (1 second, 23 milliseconds)
|
182
|
+
# 23d:05:21:23.532 (23 days, 5 hours, 21 minutes,
|
183
|
+
# 23 seconds, 532 milliseconds)
|
184
|
+
# 12. binary [base64] exmaple - [sdf789GSfsb2+3324sf2]
|
185
|
+
# 13. null
|
186
|
+
#
|
187
|
+
#
|
188
|
+
# Timezones must be specified using a valid time zone ID (ex. America/Los_Angeles), three letter
|
189
|
+
# abbreviation (ex. HST), or GMT(+/-)hh(:mm) formatted custom timezone (ex. GMT+02 or GMT+02:30)
|
190
|
+
#
|
191
|
+
# Note: SDL 1.1 will likely add a reference literal type.
|
192
|
+
#
|
193
|
+
# These types are designed to be portable across Java, .NET, and other
|
194
|
+
# popular platforms.
|
195
|
+
#
|
196
|
+
#
|
197
|
+
# SDL supports four comment types.
|
198
|
+
#
|
199
|
+
# 1. // single line comments identicle to those used in Java, C, etc. // style
|
200
|
+
# comments can occur anywhere in a line. All text after // up to the new line
|
201
|
+
# will be ignored.
|
202
|
+
# 2. # property style comments. They work the same way as //
|
203
|
+
# 3. -- separator comments useful for visually dividing content. They work the same way as //
|
204
|
+
# 4. Slash star (/*) style multiline comments. These begin with a slash
|
205
|
+
# star and end with a star slash. Everything in between is ignored.
|
206
|
+
#
|
207
|
+
#
|
208
|
+
#
|
209
|
+
# An example SDL file:
|
210
|
+
#
|
211
|
+
# # a tag having only a name
|
212
|
+
# my_tag
|
213
|
+
#
|
214
|
+
# # three tags acting as name value pairs
|
215
|
+
# first_name "Akiko"
|
216
|
+
# last_name "Johnson"
|
217
|
+
# height 68
|
218
|
+
#
|
219
|
+
# # a tag with a value list
|
220
|
+
# person "Akiko" "Johnson" 68
|
221
|
+
#
|
222
|
+
# # a tag with attributes
|
223
|
+
# person first_name="Akiko" last_name="Johnson" height=68
|
224
|
+
#
|
225
|
+
# # a tag with values and attributes
|
226
|
+
# person "Akiko" "Johnson" height=60
|
227
|
+
#
|
228
|
+
# # a tag with attributes using namespaces
|
229
|
+
# person name:first-name="Akiko" name:last-name="Johnson"
|
230
|
+
#
|
231
|
+
# # a tag with values, attributes, namespaces, and children
|
232
|
+
# my_namespace:person "Akiko" "Johnson" dimensions:height=68 {
|
233
|
+
# son "Nouhiro" "Johnson"
|
234
|
+
# daughter "Sabrina" "Johnson" location="Italy" {
|
235
|
+
# hobbies "swimming" "surfing"
|
236
|
+
# languages "English" "Italian"
|
237
|
+
# smoker false
|
238
|
+
# }
|
239
|
+
# }
|
240
|
+
#
|
241
|
+
# ------------------------------------------------------------------
|
242
|
+
# // (notice the separator style comment above...)
|
243
|
+
#
|
244
|
+
# # a log entry
|
245
|
+
# # note - this tag has two values (date_time and string) and an
|
246
|
+
# # attribute (error)
|
247
|
+
# entry 2005/11/23 10:14:23.253-GMT "Something bad happened" error=true
|
248
|
+
#
|
249
|
+
# # a long line
|
250
|
+
# mylist "something" "another" true "shoe" 2002/12/13 "rock" \
|
251
|
+
# "morestuff" "sink" "penny" 12:15:23.425
|
252
|
+
#
|
253
|
+
# # a long string
|
254
|
+
# text "this is a long rambling line of text with a continuation \
|
255
|
+
# and it keeps going and going..."
|
256
|
+
#
|
257
|
+
# # anonymous tag examples
|
258
|
+
#
|
259
|
+
# files {
|
260
|
+
# "/folder1/file.txt"
|
261
|
+
# "/file2.txt"
|
262
|
+
# }
|
263
|
+
#
|
264
|
+
# # To retrieve the files as a list of strings
|
265
|
+
# #
|
266
|
+
# # List files = tag.getChild("files").getChildrenValues("content");
|
267
|
+
# #
|
268
|
+
# # We us the name "content" because the files tag has two children, each of
|
269
|
+
# # which are anonymous tags (values with no name.) These tags are assigned
|
270
|
+
# # the name "content"
|
271
|
+
#
|
272
|
+
# matrix {
|
273
|
+
# 1 2 3
|
274
|
+
# 4 5 6
|
275
|
+
# }
|
276
|
+
#
|
277
|
+
# # To retrieve the values from the matrix (as a list of lists)
|
278
|
+
# #
|
279
|
+
# # List rows = tag.getChild("matrix").getChildrenValues("content");
|
280
|
+
#
|
281
|
+
#
|
282
|
+
# Example of getting the "location" attribute from the "daughter" tag
|
283
|
+
# above (ignoring exceptions)
|
284
|
+
#
|
285
|
+
# Tag root = new Tag("root").read("myfile.sdl");
|
286
|
+
# Tag daughter = root.getChild("daughter", true); // recursive search
|
287
|
+
# String location = daughter.getAttribute("location").toString();
|
288
|
+
#
|
289
|
+
# SDL is normally stored in a file with the .sdl extension. These files
|
290
|
+
# should always be encoded using UTF8. SDL fully supports unicode in
|
291
|
+
# identifiers and literals.
|
292
|
+
#
|
293
|
+
# @author Daniel Leuck
|
294
|
+
#
|
295
|
+
class Tag
|
296
|
+
|
297
|
+
#
|
298
|
+
# the name of this Tag
|
299
|
+
#
|
300
|
+
attr_reader :name
|
301
|
+
|
302
|
+
#
|
303
|
+
# the namespace of this Tag or an empty string when there is no namespace.
|
304
|
+
#
|
305
|
+
attr_reader :namespace
|
306
|
+
|
307
|
+
# Creates an empty tag in the given namespace. If the +namespace+ is null
|
308
|
+
# it will be coerced to an empty String.
|
309
|
+
#
|
310
|
+
# +namespace+:: the namespace for this tag
|
311
|
+
# +name+:: the name of this tag
|
312
|
+
#
|
313
|
+
# Throws ArgumentError if the name is not a legal SDL identifier
|
314
|
+
# (see SDL#validate_identifier) or the namespace is non-blank
|
315
|
+
# and is not a legal SDL identifier.
|
316
|
+
#
|
317
|
+
def initialize(name, namespace = "")
|
318
|
+
namespace = namespace.to_s
|
319
|
+
SDL4R.validate_identifier(namespace) unless namespace.empty?
|
320
|
+
@namespace = namespace
|
321
|
+
|
322
|
+
name = name.to_s.strip
|
323
|
+
raise ArgumentError, "Tag name cannot be null or empty" if name.empty?
|
324
|
+
SDL4R.validate_identifier(name)
|
325
|
+
@name = name
|
326
|
+
|
327
|
+
@children = []
|
328
|
+
@values = []
|
329
|
+
|
330
|
+
# a Hash of Hash : {namespace => {name => value}}
|
331
|
+
# The default namespace is represented by an empty string.
|
332
|
+
@attributesByNamespace = {}
|
333
|
+
end
|
334
|
+
|
335
|
+
# Add a child to this Tag.
|
336
|
+
#
|
337
|
+
# +child+:: The child to add
|
338
|
+
#
|
339
|
+
# Returns the added child.
|
340
|
+
#
|
341
|
+
def add_child(child)
|
342
|
+
@children.push(child)
|
343
|
+
return child
|
344
|
+
end
|
345
|
+
|
346
|
+
# Adds the given object as a child if it is a +Tag+, as an attribute if it is a Hash
|
347
|
+
# {key => value} (supports namespaces), or as a value otherwise.
|
348
|
+
#
|
349
|
+
# Returns +self+.
|
350
|
+
#
|
351
|
+
def <<(o)
|
352
|
+
if o.is_a?(Tag)
|
353
|
+
add_child(o)
|
354
|
+
elsif o.is_a?(Hash)
|
355
|
+
o.each_pair { |key, value|
|
356
|
+
namespace, key = key.split(/:/) if key.match(/:/)
|
357
|
+
namespace ||= ""
|
358
|
+
set_attribute(key, value, namespace)
|
359
|
+
}
|
360
|
+
else
|
361
|
+
add_value(o)
|
362
|
+
end
|
363
|
+
return self
|
364
|
+
end
|
365
|
+
|
366
|
+
# Remove a child from this Tag
|
367
|
+
#
|
368
|
+
# +child+:: the child to remove
|
369
|
+
#
|
370
|
+
# Returns true if the child exists and is removed
|
371
|
+
#
|
372
|
+
def remove_child(child)
|
373
|
+
return !@children.delete(child).nil?
|
374
|
+
end
|
375
|
+
|
376
|
+
# Removes all children.
|
377
|
+
#
|
378
|
+
def clear_children
|
379
|
+
@children = []
|
380
|
+
end
|
381
|
+
|
382
|
+
#
|
383
|
+
# A convenience method that sets the first value in the value list. See
|
384
|
+
# {@link #addValue(Object)} for legal types.
|
385
|
+
#
|
386
|
+
# +value+:: The value to be set.
|
387
|
+
# @throws IllegalArgumentException if the value is not a legal SDL type
|
388
|
+
#
|
389
|
+
def value=(value)
|
390
|
+
@values[0] = SDL4R.coerce_or_fail(value)
|
391
|
+
end
|
392
|
+
|
393
|
+
#
|
394
|
+
# A convenience method that returns the first value.
|
395
|
+
#
|
396
|
+
def value
|
397
|
+
@values[0]
|
398
|
+
end
|
399
|
+
|
400
|
+
# Returns the number of children Tag.
|
401
|
+
#
|
402
|
+
def child_count
|
403
|
+
@children.size
|
404
|
+
end
|
405
|
+
|
406
|
+
# Returns an Array of the children Tags of this Tag.
|
407
|
+
#
|
408
|
+
# +recursive+:: if true children and all descendants will be returned. False by default.
|
409
|
+
# +name+:: if not nil, only children having this name will be returned. Nil by default.
|
410
|
+
# +namespace+:: if not nil, only children having this namespace will be returned.
|
411
|
+
# Nil by default.
|
412
|
+
#
|
413
|
+
def children(recursive = false, name = nil, namespace = nil, &block)
|
414
|
+
if block_given?
|
415
|
+
each_child(recursive, name, namespace, &block)
|
416
|
+
|
417
|
+
else
|
418
|
+
unless recursive or name or namespace
|
419
|
+
@children
|
420
|
+
|
421
|
+
else
|
422
|
+
result = []
|
423
|
+
each_child(recursive, name, namespace) { |child|
|
424
|
+
result << child
|
425
|
+
}
|
426
|
+
return result
|
427
|
+
end
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
# Returns the values of all the children with the given +name+. If the child has
|
432
|
+
# more than one value, all the values will be added as a list. If the child
|
433
|
+
# has no value, +nil+ will be added. The search is not recursive.
|
434
|
+
#
|
435
|
+
# +name+:: if nil, all children are considered (nil by default).
|
436
|
+
def children_values(name = nil)
|
437
|
+
children_values = []
|
438
|
+
each_child(false, name) { |child|
|
439
|
+
case child.values.size
|
440
|
+
when 0
|
441
|
+
children_values << nil
|
442
|
+
when 1
|
443
|
+
children_values << child.value
|
444
|
+
else
|
445
|
+
children_values << child.values
|
446
|
+
end
|
447
|
+
}
|
448
|
+
return children_values
|
449
|
+
end
|
450
|
+
|
451
|
+
# Get the first child with the given name, optionally using a recursive search.
|
452
|
+
#
|
453
|
+
# +name+:: the name of the child Tag. If +nil+, the first child is returned (+nil+ if there are
|
454
|
+
# no children at all).
|
455
|
+
#
|
456
|
+
# Returns The first child tag having the given name or +nil+ if no such child exists
|
457
|
+
#
|
458
|
+
def child(name = nil, recursive = false)
|
459
|
+
unless name
|
460
|
+
return @children.first
|
461
|
+
else
|
462
|
+
each_child(recursive, name) { |child| return child }
|
463
|
+
end
|
464
|
+
end
|
465
|
+
|
466
|
+
# Enumerates the children +Tag+s of this Tag and calls the given block
|
467
|
+
# providing it the child as parameter.
|
468
|
+
#
|
469
|
+
# +recursive+:: if true, enumerate grand-children, etc, recursively
|
470
|
+
# +name+:: if not nil, indicates the name of the children to enumerate
|
471
|
+
# +namespace+:: if not nil, indicates the namespace of the children to enumerate
|
472
|
+
#
|
473
|
+
def each_child(recursive = false, name = nil, namespace = nil, &block)
|
474
|
+
@children.each do |child|
|
475
|
+
if (name.nil? or child.name == name) and
|
476
|
+
(namespace.nil? or child.namespace == namespace)
|
477
|
+
yield child
|
478
|
+
end
|
479
|
+
|
480
|
+
child.children(recursive, name, namespace, &block) if recursive
|
481
|
+
end
|
482
|
+
return nil
|
483
|
+
end
|
484
|
+
private :each_child
|
485
|
+
|
486
|
+
# Adds a value to this Tag. The allowable types are String, Number,
|
487
|
+
# Boolean, Character, byte[], Byte[] (coerced to byte[]), Calendar,
|
488
|
+
# Date (coerced to Calendar), and null. Passing any other type will
|
489
|
+
# result in an IllegalArgumentException.
|
490
|
+
#
|
491
|
+
# +v+:: The value to add
|
492
|
+
#
|
493
|
+
# Raises a +ArgumentError+ if the value is not a legal SDL type
|
494
|
+
#
|
495
|
+
def add_value(v)
|
496
|
+
@values.push(SDL4R::coerce_or_fail(v))
|
497
|
+
end
|
498
|
+
|
499
|
+
# Returns true if +v+ is a value of this Tag's.
|
500
|
+
#
|
501
|
+
def has_value(v)
|
502
|
+
@values.include?(v)
|
503
|
+
end
|
504
|
+
|
505
|
+
# Remove a value from this Tag.
|
506
|
+
#
|
507
|
+
# +v+:: The value to remove
|
508
|
+
#
|
509
|
+
# Returns true If the value exists and is removed
|
510
|
+
#
|
511
|
+
def remove_value(v)
|
512
|
+
return !@values.delete(v).nil?
|
513
|
+
end
|
514
|
+
|
515
|
+
# Removes all values.
|
516
|
+
#
|
517
|
+
def clear_values
|
518
|
+
@values = []
|
519
|
+
end
|
520
|
+
|
521
|
+
# Returns an Array of the values of this Tag
|
522
|
+
def values
|
523
|
+
if block_given?
|
524
|
+
@values.each { |v| yield v }
|
525
|
+
else
|
526
|
+
return @values
|
527
|
+
end
|
528
|
+
end
|
529
|
+
|
530
|
+
#
|
531
|
+
# Set the values for this tag. See {@link #addValue(Object)} for legal
|
532
|
+
# value types.
|
533
|
+
#
|
534
|
+
# +values+:: The new values
|
535
|
+
# @throws IllegalArgumentException if the collection contains any values
|
536
|
+
# which are not legal SDL types
|
537
|
+
#
|
538
|
+
def values=(someValues)
|
539
|
+
@values.clear()
|
540
|
+
someValues.to_a.each { |v|
|
541
|
+
# this is required to ensure validation of types
|
542
|
+
add_value(v)
|
543
|
+
}
|
544
|
+
end
|
545
|
+
|
546
|
+
#
|
547
|
+
# Set an attribute in the given namespace for this tag. The allowable
|
548
|
+
# attribute value types are the same as those allowed for
|
549
|
+
# {@link #addValue(Object)}
|
550
|
+
#
|
551
|
+
# +namespace+:: The namespace for this attribute
|
552
|
+
# +key+:: The attribute key
|
553
|
+
# +value+:: The attribute value
|
554
|
+
# @throws IllegalArgumentException if the key is not a legal SDL
|
555
|
+
# identifier (see {@link SDL#validateIdentifier(String)}), or the
|
556
|
+
# namespace is non-blank and is not a legal SDL identifier, or the
|
557
|
+
# value is not a legal SDL type
|
558
|
+
#
|
559
|
+
def set_attribute(key, value, namespace = "")
|
560
|
+
SDL4R.validate_identifier(namespace) unless namespace.empty?
|
561
|
+
SDL4R.validate_identifier(key)
|
562
|
+
|
563
|
+
attributes = @attributesByNamespace[namespace]
|
564
|
+
|
565
|
+
if attributes.nil?
|
566
|
+
attributes = {}
|
567
|
+
@attributesByNamespace[namespace] = attributes
|
568
|
+
end
|
569
|
+
|
570
|
+
attributes[key] = SDL4R.coerce_or_fail(value)
|
571
|
+
end
|
572
|
+
|
573
|
+
# Returns the attribute of the specified +namespace+ of specified +key+ or +nil+ if not found.
|
574
|
+
#
|
575
|
+
# +namespace+:: the default namespace ("") by default
|
576
|
+
#
|
577
|
+
def attribute(key, namespace = "")
|
578
|
+
attributes = @attributesByNamespace[namespace]
|
579
|
+
return attributes.nil? ? nil : attributes[key]
|
580
|
+
end
|
581
|
+
|
582
|
+
# Indicates whether the specified attribute exists in this Tag.
|
583
|
+
#
|
584
|
+
# +key+:: key of the attribute
|
585
|
+
# +namespace+:: namespace of the attribute ("", the default namespace, by default)
|
586
|
+
#
|
587
|
+
def has_attribute(key, namespace = "")
|
588
|
+
attributes = @attributesByNamespace[namespace]
|
589
|
+
return attributes.nil? ? false : attributes.has_key?(key)
|
590
|
+
end
|
591
|
+
|
592
|
+
# Returns a copy of the Hash of the attributes of the specified +namespace+ (default is all).
|
593
|
+
#
|
594
|
+
# +namespace+:: namespace of the returned attributes. If nil, all attributes are returned with
|
595
|
+
# qualified names (e.g. "meat:color"). If "" attributes of the default namespace are returned.
|
596
|
+
#
|
597
|
+
def attributes(namespace = nil, &block)
|
598
|
+
if block_given?
|
599
|
+
each_attribute(namespace, &block)
|
600
|
+
|
601
|
+
else
|
602
|
+
if namespace.nil?
|
603
|
+
hash = {}
|
604
|
+
|
605
|
+
each_attribute(nil) do | key, value, namespace |
|
606
|
+
qualified_name = namespace.empty? ? key : namespace + ':' + key
|
607
|
+
hash[qualified_name] = value
|
608
|
+
end
|
609
|
+
|
610
|
+
return hash
|
611
|
+
|
612
|
+
else
|
613
|
+
hash = @attributesByNamespace[namespace]
|
614
|
+
return hash.clone
|
615
|
+
end
|
616
|
+
end
|
617
|
+
end
|
618
|
+
|
619
|
+
# Removes the attribute, whose name and namespace are specified.
|
620
|
+
#
|
621
|
+
# +key+:: name of the removed atribute
|
622
|
+
# +namespace+:: namespace of the removed attribute (equal to "", default namespace, by default)
|
623
|
+
#
|
624
|
+
# Returns the value of the removed attribute or +nil+ if it didn't exist.
|
625
|
+
#
|
626
|
+
def remove_attribute(key, namespace = "")
|
627
|
+
attributes = @attributesByNamespace[namespace]
|
628
|
+
return attributes.nil? ? nil : attributes.delete(key)
|
629
|
+
end
|
630
|
+
|
631
|
+
# Clears the attributes of the specified namespace or all the attributes if +namespace+ is
|
632
|
+
# +nil+.
|
633
|
+
#
|
634
|
+
def clear_attributes(namespace = nil)
|
635
|
+
if namespace.nil?
|
636
|
+
@attributesByNamespace.clear
|
637
|
+
else
|
638
|
+
@attributesByNamespace.delete(namespace)
|
639
|
+
end
|
640
|
+
end
|
641
|
+
|
642
|
+
# Enumerates the attributes for the specified +namespace+.
|
643
|
+
# If no +namespace+ is specified, enumerates the attribute of the default
|
644
|
+
# namespace. If +namespace+ is nil, enumerates the attributes of all
|
645
|
+
# namespaces.
|
646
|
+
#
|
647
|
+
def each_attribute(namespace = "", &block)
|
648
|
+
if namespace.nil?
|
649
|
+
@attributesByNamespace.each_key { |a_namespace| each_attribute(a_namespace, &block) }
|
650
|
+
|
651
|
+
else
|
652
|
+
attributes = @attributesByNamespace[namespace]
|
653
|
+
unless attributes.nil?
|
654
|
+
attributes.each_pair do |key, value|
|
655
|
+
yield key, value, namespace
|
656
|
+
end
|
657
|
+
end
|
658
|
+
end
|
659
|
+
end
|
660
|
+
private :each_attribute
|
661
|
+
|
662
|
+
# Sets all the attributes of a +namespace+ for this Tag in one operation.
|
663
|
+
# See # #add_value for allowable attribute value types.
|
664
|
+
#
|
665
|
+
# +attributes+:: a Hash where keys are attribute keys
|
666
|
+
# +namespace+:: "" (default namespace) by default
|
667
|
+
#
|
668
|
+
# Raises an +ArgumentError+ if any key in the map is not a legal SDL
|
669
|
+
# identifier (see SDL#validate_identifier), or any value
|
670
|
+
# is not a legal SDL type.
|
671
|
+
#
|
672
|
+
def set_attributes(attribute_hash, namespace = "")
|
673
|
+
return if attribute_hash.nil? or attribute_hash.empty?
|
674
|
+
|
675
|
+
attributes = @attributesByNamespace[namespace]
|
676
|
+
if attributes.nil?
|
677
|
+
attributes = {}
|
678
|
+
@attributesByNamespace[namespace] = attributes
|
679
|
+
else
|
680
|
+
attributes.clear()
|
681
|
+
end
|
682
|
+
|
683
|
+
attribute_hash.each_pair do |key, value|
|
684
|
+
# Calling set_attribute() is required to ensure validations
|
685
|
+
set_attribute(key, value)
|
686
|
+
end
|
687
|
+
end
|
688
|
+
|
689
|
+
# Sets all the attributes of the default namespace for this Tag in one
|
690
|
+
# operation.
|
691
|
+
#
|
692
|
+
# See #set_attributes
|
693
|
+
#
|
694
|
+
def attributes=(attribute_hash)
|
695
|
+
set_attributes(attribute_hash, "")
|
696
|
+
end
|
697
|
+
|
698
|
+
# Sets the name of this Tag.
|
699
|
+
#
|
700
|
+
# Raises +ArgumentError+ if the name is not a legal SDL
|
701
|
+
# identifier (see SDL#validate_identifier)
|
702
|
+
|
703
|
+
def name=(a_name)
|
704
|
+
a_name = a_name.to_s
|
705
|
+
SDL4R.validate_identifier(a_name)
|
706
|
+
@name = a_name
|
707
|
+
end
|
708
|
+
|
709
|
+
# The namespace to set. null will be coerced to the empty string.
|
710
|
+
#
|
711
|
+
# Raises +ArgumentError+ if the namespace is non-blank and is not
|
712
|
+
# a legal SDL identifier (see {@link SDL#validate_identifier(String)})
|
713
|
+
|
714
|
+
def namespace=(a_namespace)
|
715
|
+
a_namespace = a_namespace.to_s
|
716
|
+
SDL4R.validate_identifier(a_namespace) unless a_namespace.empty?
|
717
|
+
@namespace = a_namespace
|
718
|
+
end
|
719
|
+
|
720
|
+
#
|
721
|
+
# Add all the tags specified in the file at the given URL to this Tag.
|
722
|
+
#
|
723
|
+
# +url+:: A UTF8 encoded .sdl file
|
724
|
+
# @throws IOException If there is an IO problem reading the source
|
725
|
+
# @throws ParseException If the SDL input is malformed
|
726
|
+
# Returns This tag after adding all the children read from the reader
|
727
|
+
#
|
728
|
+
# public Tag read(URL url) throws IOException, SDLParseException {
|
729
|
+
# return read(new InputStreamReader(url.openStream(), "UTF8"));
|
730
|
+
# }
|
731
|
+
|
732
|
+
#
|
733
|
+
# Add all the tags specified in the given file to this Tag.
|
734
|
+
#
|
735
|
+
# +file+:: A UTF8 encoded .sdl file
|
736
|
+
# @throws IOException If there is an IO problem reading the source
|
737
|
+
# @throws ParseException If the SDL input is malformed
|
738
|
+
# Returns This tag after adding all the children read from the reader
|
739
|
+
#
|
740
|
+
# public Tag read(File file) throws IOException, SDLParseException {
|
741
|
+
# return read(new InputStreamReader(new FileInputStream(file), "UTF8"));
|
742
|
+
# }
|
743
|
+
|
744
|
+
#
|
745
|
+
# Add all the tags specified in the given String to this Tag.
|
746
|
+
#
|
747
|
+
# +text+:: An SDL string
|
748
|
+
# @throws ParseException If the SDL input is malformed
|
749
|
+
# Returns This tag after adding all the children read from the reader
|
750
|
+
#
|
751
|
+
# public Tag read(String text) throws SDLParseException {
|
752
|
+
# try {
|
753
|
+
# return read(new StringReader(text));
|
754
|
+
# } catch(IOException ioe) {
|
755
|
+
# // Cannot happen
|
756
|
+
# throw new InternalError("IOExceptio reading a String");
|
757
|
+
# }
|
758
|
+
# }
|
759
|
+
|
760
|
+
# Adds all the tags specified in the given +IO+ or String to this Tag.
|
761
|
+
#
|
762
|
+
# Returns this Tag after adding all the children read from _input_.
|
763
|
+
#
|
764
|
+
def read(input)
|
765
|
+
if input.is_a?(String)
|
766
|
+
io = StringIO.new(input)
|
767
|
+
else
|
768
|
+
io = input
|
769
|
+
end
|
770
|
+
|
771
|
+
Parser.new(io).parse.each do |tag|
|
772
|
+
add_child(tag)
|
773
|
+
end
|
774
|
+
|
775
|
+
return self
|
776
|
+
end
|
777
|
+
|
778
|
+
# Write this tag out to the given IO or string (optionally clipping the root.)
|
779
|
+
#
|
780
|
+
# +writer+:: The writer to which we will write this tag
|
781
|
+
# +includeRoot+:: If true this tag will be written out as the root
|
782
|
+
# element, if false only the children will be written
|
783
|
+
#
|
784
|
+
def write(output, include_root = false)
|
785
|
+
io = (output.is_a?(String))? StringIO.new(output) : output
|
786
|
+
|
787
|
+
if include_root
|
788
|
+
io << to_s
|
789
|
+
else
|
790
|
+
each_child do |child, index|
|
791
|
+
io << $/ if index > 0
|
792
|
+
child.write(io)
|
793
|
+
end
|
794
|
+
end
|
795
|
+
|
796
|
+
io.close()
|
797
|
+
end
|
798
|
+
|
799
|
+
#
|
800
|
+
# Get a String representation of this SDL Tag. This method returns a
|
801
|
+
# complete description of the Tag's state using SDL (i.e. the output can
|
802
|
+
# be parsed by {@link #read(String)})
|
803
|
+
#
|
804
|
+
# Returns A string representation of this tag using SDL
|
805
|
+
#
|
806
|
+
def to_s
|
807
|
+
to_string
|
808
|
+
end
|
809
|
+
|
810
|
+
#
|
811
|
+
# +linePrefix+:: A prefix to insert before every line.
|
812
|
+
# Returns A string representation of this tag using SDL
|
813
|
+
#
|
814
|
+
# TODO: break up long lines using the backslash
|
815
|
+
#
|
816
|
+
def to_string(line_prefix = "", indent = "\t")
|
817
|
+
line_prefix = "" if line_prefix.nil?
|
818
|
+
s = ""
|
819
|
+
s << line_prefix
|
820
|
+
|
821
|
+
if name == "content" && namespace.empty?
|
822
|
+
skip_value_space = true
|
823
|
+
else
|
824
|
+
skip_value_space = false
|
825
|
+
s << "#{namespace}:" unless namespace.empty?
|
826
|
+
s << name
|
827
|
+
end
|
828
|
+
|
829
|
+
# output values
|
830
|
+
values do |value|
|
831
|
+
if skip_value_space
|
832
|
+
skip_value_space = false
|
833
|
+
else
|
834
|
+
s << " "
|
835
|
+
end
|
836
|
+
s << SDL4R.format(value, true, line_prefix, indent)
|
837
|
+
end
|
838
|
+
|
839
|
+
# output attributes
|
840
|
+
unless @attributesByNamespace.empty?
|
841
|
+
all_attributes_hash = attributes(nil)
|
842
|
+
all_attributes_array = all_attributes_hash.sort { |a, b|
|
843
|
+
namespace1, name1 = a[0].split(':')
|
844
|
+
namespace1, name1 = "", namespace1 if name1.nil?
|
845
|
+
namespace2, name2 = b[0].split(':')
|
846
|
+
namespace2, name2 = "", namespace2 if name2.nil?
|
847
|
+
|
848
|
+
diff = namespace1 <=> namespace2
|
849
|
+
diff == 0 ? name1 <=> name2 : diff
|
850
|
+
}
|
851
|
+
all_attributes_array.each do |attribute_name, attribute_value|
|
852
|
+
s << " " << attribute_name << '=' << SDL4R.format(attribute_value, true)
|
853
|
+
end
|
854
|
+
end
|
855
|
+
|
856
|
+
# output children
|
857
|
+
unless @children.empty?
|
858
|
+
s << " {#{$/}"
|
859
|
+
children_to_string(line_prefix + indent, s)
|
860
|
+
s << line_prefix << ?}
|
861
|
+
end
|
862
|
+
|
863
|
+
return s
|
864
|
+
end
|
865
|
+
|
866
|
+
# Returns a string representation of the children tags.
|
867
|
+
#
|
868
|
+
# +linePrefix+:: A prefix to insert before every line.
|
869
|
+
# +s+:: a String that receives the string representation
|
870
|
+
#
|
871
|
+
# TODO: break up long lines using the backslash
|
872
|
+
#
|
873
|
+
def children_to_string(line_prefix = "", s = "")
|
874
|
+
@children.each do |child|
|
875
|
+
s << child.to_string(line_prefix) << $/
|
876
|
+
end
|
877
|
+
|
878
|
+
return s
|
879
|
+
end
|
880
|
+
|
881
|
+
# Returns true if this tag (including all of its values, attributes, and
|
882
|
+
# children) is equivalent to the given tag.
|
883
|
+
#
|
884
|
+
# Returns true if the tags are equivalet
|
885
|
+
#
|
886
|
+
def eql?(o)
|
887
|
+
# this is safe because to_string() dumps the full state
|
888
|
+
return o.is_a?(Tag) && o.to_string == to_string;
|
889
|
+
end
|
890
|
+
alias_method :==, :eql?
|
891
|
+
|
892
|
+
# Returns The hash (based on the output from toString())
|
893
|
+
#
|
894
|
+
def hash
|
895
|
+
return to_string.hash
|
896
|
+
end
|
897
|
+
|
898
|
+
# Returns a string containing an XML representation of this tag. Values
|
899
|
+
# will be represented using _val0, _val1, etc.
|
900
|
+
#
|
901
|
+
# Returns An XML String describing this Tag
|
902
|
+
# +linePrefix+:: A prefix to insert before every line.
|
903
|
+
# Returns A String containing an XML representation of this tag. Values
|
904
|
+
# will be represented using _val0, _val1, etc.
|
905
|
+
#
|
906
|
+
def to_xml_string(linePrefix = "")
|
907
|
+
linePrefix = "" if linePrefix.nil?
|
908
|
+
|
909
|
+
s = ""
|
910
|
+
s << linePrefix << ?<
|
911
|
+
s << "#{namespace}:" unless namespace.empty?
|
912
|
+
s << name
|
913
|
+
|
914
|
+
# output values
|
915
|
+
unless @values.empty?
|
916
|
+
i = 0
|
917
|
+
@values.each do |value|
|
918
|
+
s << " _val" << i.to_s << "=\"" << SDL4R.format(value, false) << "\""
|
919
|
+
i += 1
|
920
|
+
end
|
921
|
+
end
|
922
|
+
|
923
|
+
# output attributes
|
924
|
+
unless @attributes.empty?
|
925
|
+
@attributes.each do |attribute_name, attribute_value|
|
926
|
+
s << " "
|
927
|
+
attribute_namespace = @attributeToNamespace[attribute_name]
|
928
|
+
s << "#{attribute_namespace}:" unless attribute_namespace.empty?
|
929
|
+
s << attribute_name << "=\"" << SDL4R.format(attribute_value, false) << ?"
|
930
|
+
end
|
931
|
+
end
|
932
|
+
|
933
|
+
if @children.empty?
|
934
|
+
s << "/>"
|
935
|
+
else
|
936
|
+
s << ">\n"
|
937
|
+
@children.each do |child|
|
938
|
+
s << child.to_xml_string(linePrefix + " ") << ?\n
|
939
|
+
end
|
940
|
+
|
941
|
+
s << linePrefix << "</"
|
942
|
+
s << "#{namespace}:" unless namespace.empty?
|
943
|
+
s << name << ?>
|
944
|
+
end
|
945
|
+
|
946
|
+
return s
|
947
|
+
end
|
948
|
+
end
|
949
|
+
end
|