sdl4r 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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: &#92;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