sdl4r 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|