udat 1.2.1 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/example.udat +242 -0
  2. data/lib/udat.rb +946 -840
  3. metadata +3 -2
@@ -0,0 +1,242 @@
1
+ This is an example for a UDAT document.
2
+
3
+ [UDAT example|
4
+
5
+
6
+ Sample configuration file:
7
+
8
+ [sample config v1.0|
9
+
10
+ <network> [
11
+ <max connections> [256]
12
+ <reverse lookups> [yes]
13
+ ]
14
+
15
+ <locale> [
16
+ <language> [de] german language
17
+ <timezone> [CET] and CET timezone
18
+ Comments can appear almost anywhere.
19
+ ]
20
+
21
+ <access control> [
22
+ <allowed> [
23
+ [user|martin]
24
+ [user|max]
25
+ [group|admins]
26
+ [ip4network|
27
+ <address> [192.168.0.0]
28
+ <netmask> [255.255.255.0]
29
+ ]
30
+ ]
31
+ <blocked> [~] The tilde symbol denotes an empty collection.
32
+ ]
33
+
34
+ <misc> [
35
+ <symbol for homedir> [\~] Not an empty collection but the scalar
36
+ value "~".
37
+ ]
38
+
39
+ <address mappings> [
40
+
41
+ <email|
42
+ <user> [jan.behrens]
43
+ <domain> [flexiguided.de]
44
+ >
45
+ [mbox|jan]
46
+
47
+ <email|
48
+ <user> [glob|*]
49
+ <domain> [flexiguided.de]
50
+ >
51
+ [mbox|catchall]
52
+
53
+ ]
54
+
55
+ <logging> [
56
+ <verbosity> [high]
57
+ \## <verbosity> [debug] ## Uncomment this for debug output.
58
+ <destination> [file|/var/log/sample.log]
59
+ ]
60
+
61
+ ]
62
+
63
+
64
+ General Information:
65
+
66
+ Every UDAT node can either be a scalar or a collection. Scalars have
67
+ content represented by a single string, while collections contain other
68
+ UDAT nodes.
69
+
70
+ Every UDAT node may be tagged, that means a meta-information in form of
71
+ a string is added. This information can be seen as type information.
72
+
73
+ Comments may appear anywhere in the document except in tags or scalars.
74
+
75
+ The following 7 characters are to be escaped with a backslash, unless
76
+ being used for their special meaning:
77
+
78
+ \< \> \[ \] \| \~ \\
79
+
80
+
81
+ Scalar values:
82
+
83
+ Any string without square or angle brackets or the tilde symbol is
84
+ considered to be a scalar:
85
+
86
+ [Hello World!]
87
+
88
+ You may add an additional tag, by preceding the string with the tag and
89
+ the pipe symbol:
90
+
91
+ [UTF-8|Hello World!]
92
+
93
+ Interpretation of tags is task of the application, there are no standard
94
+ tags defined.
95
+
96
+ Numbers are stored as strings too.
97
+
98
+ [23]
99
+
100
+ But they may be tagged for example with "integer", to clarify, that they
101
+ are integer numbers.
102
+
103
+ [integer|23]
104
+
105
+ You could store rational numbers like this:
106
+
107
+ [rational|2/3]
108
+
109
+
110
+ Collections:
111
+
112
+ Any string which includes either square or angle brackets or a tilde
113
+ symbol is considered to be a collection. Each entry in a collection
114
+ consists of a value and optionally a key associated with that value.
115
+ The key is written in angle brackets and being followed by the value in
116
+ square brackets. If the value has no key, the angle brackets are not
117
+ written.
118
+
119
+ Collection without keys: [ [1] [2] [3] ]
120
+ Collection with keys: [ <A>[1] <B>[2] <C>[3] ]
121
+
122
+ Collections may also contain both values with and without keys:
123
+
124
+ [ [0] <A>[1] <B>[2] <C>[3] ] ("0" has no key associated with it.)
125
+
126
+ Tags for collections are provided in the same way as for scalars:
127
+
128
+ [map| [0] <A>[1] <B>[2] <C>[3] ] (A collection tagged with "map")
129
+
130
+ Comments are allowed between the entries in brackets (as long as
131
+ characters with a special meaning are escaped):
132
+
133
+ [ comment [1] comment [2] comment [3] \[comment\] ]
134
+
135
+ Empty collections must include the tilde symbol, to avoid ambiguity with
136
+ scalars:
137
+
138
+ [ ] scalar containing three spaces
139
+ [ ~ ] empty collection
140
+ [list of numbers| ~ ] empty collection, tagged with "list of numbers"
141
+ [ ~ comment ] empty collection with a comment
142
+ [ comment ~ ] another empty collection with a comment
143
+ [~~] empty collection too (multiple ~ are allowed)
144
+
145
+ It is also allowed to write a tilde symbol in non-empty arrays. In that
146
+ case the tilde symbol is ignored.
147
+
148
+ [~ [1] [2] [3] ] Array containing "1", "2" and "3"
149
+
150
+ So the tilde symbol can be seen as a marker for collections, which is
151
+ mandatory in empty collections and optional in non-empty collections.
152
+
153
+ There is no standardized way to express, whether the order of a
154
+ collection is significant or not. It is task of the application to decide
155
+ that, so [[1][2][3]] could be seen as an ordered list of numbers or as a
156
+ set of numbers where the order is not significant. An application can use
157
+ tags to store the information, if neccessary, for example:
158
+ [set|[1][2][3]] vs. [array|[1][2][3]]
159
+
160
+ Examples of more complicated collections:
161
+
162
+ [group| <name>[Sample group] <members> [
163
+ [person| <firstname>[Max] <lastname>[Mustermann] ]
164
+ [person| <firstname>[Martin] <lastname>[html|M&uuml;ller] ]
165
+ ] ]
166
+
167
+ [ <&> [html|&amp;]
168
+ <"> [html|&quot;]
169
+ <\<> [html|&lt;]
170
+ <\>> [html|&gt;] ]
171
+
172
+ [ <color combination|[red][blue]> [nice]
173
+ <color combination|[red][magenta]> [ugly] ]
174
+
175
+ [ < <red>[on] <yellow>[off] <green>[off] > [stop]
176
+ < <red>[on] <yellow>[on] <green>[off] > [attention]
177
+ < <red>[off] <yellow>[off] <green>[on] > [go]
178
+ < <red>[off] <yellow>[on] <green>[off] > [prepare to stop] ]
179
+
180
+
181
+ Structured meta information:
182
+
183
+ Don't abuse tags to store complicated meta information like this:
184
+
185
+ \#WRONG# [string charset=ISO-8859-1 language=EN|Hello World!] #WRONG#
186
+
187
+ If you need to store more complicated meta information, you should do
188
+ that by using ordinary key/value pairs:
189
+
190
+ [string/w/metainfo v1.0|
191
+ <charset> [ISO-8859-1]
192
+ <language> [en]
193
+ <style> [folded]
194
+ [
195
+ Hello
196
+ World!
197
+ ]
198
+ ]
199
+
200
+ The tag "string/w/metainfo v1.0" is used only as the basic type
201
+ information (for the meta data and payload) here, the meta-data itself is
202
+ stored in key/value pairs, while the content is a value without key. An
203
+ application could interpret the "style" = "folded" information as a hint
204
+ to remove indentation and single line breaks, similar to folded scalars
205
+ in YAML, however this is application dependent and not part of the UDAT
206
+ format specification.
207
+
208
+
209
+ Binary safe parts and commenting-out:
210
+
211
+ There are two alternatives to disable interpretation of special
212
+ characters, without needing to add a backslash before each epecial
213
+ character:
214
+
215
+ Escape for a fixed length of bytes (useful to include binary blobs):
216
+
217
+ \$4$<>[]
218
+
219
+ Escape until boundary:
220
+
221
+ \## Here, no interpretation of <>[]|~\ is done. ##
222
+
223
+ \#boundary#
224
+ Same as above, but ## may be contained
225
+ and end is marked with: #boundary#
226
+
227
+ By using the \## ... ## syntax you can easily comment out parts of a
228
+ document:
229
+
230
+ [sample config v1.0|
231
+ <access control> [
232
+ <allowed> [ [user|martin] \##[user|max]## [group|admins] ]
233
+ ]
234
+ ]
235
+
236
+ But note that you have to specify a different boundary, if ## is
237
+ contained at any place (including scalar values) inside the commented-out
238
+ part.
239
+
240
+
241
+ ]
242
+
@@ -2,7 +2,6 @@
2
2
 
3
3
  require 'monitor'
4
4
 
5
-
6
5
  # Copyright (c) 2007 FlexiGuided GmbH, Berlin
7
6
  #
8
7
  # Author: Jan Behrens
@@ -11,1002 +10,1109 @@ require 'monitor'
11
10
  #
12
11
  # -----
13
12
  #
14
- # Abstract class of an UDAT object. UDAT objects basically consist of a tag
15
- # and the content. The tag can be a String or be nil. The content is either
16
- # a scalar value stored as a String, or an ordered/unordered collection of
17
- # values (where each value can optionally have a key associated with it).
18
- # Keys and values of collections are always UDAT objects too.
13
+ # This module provides data structures to represent nodes of UDAT
14
+ # documents, a data format offering a generic basis for data storage and
15
+ # transmission, while being both easily readable by humans and machines.
16
+ # The format is comparable to formats like XML or YAML, but due to its
17
+ # simplicity is much more easy to parse.
19
18
  #
20
- # UDAT objects can most easily be constructed from other ruby objects by
21
- # using the Object#to_udat method.
19
+ # UDAT documents can be generated by converting any Object to a Udat::Node,
20
+ # by calling Object#to_udat, and then encoding them by calling
21
+ # Udat::Node#encode_document.
22
22
  #
23
- # By calling the method Udat#encode, the UDAT object is encoded in a both
24
- # easily human readable and easily machine readable format. The output can
25
- # be later parsed by String#parse_udat, or if it is enclosed in square
26
- # brackets by IO#read_udat.
27
- class Udat
23
+ # UDAT documents can be parsed by calling String#parse_udat_document.
24
+ module Udat
28
25
 
29
- include MonitorMixin
26
+ # Error raised by Udat::Collection#fetch,
27
+ # when a fetched value doesn't have the expected tag (i.e. type).
28
+ class UdatTagMismatch < StandardError; end
29
+ # Error raised by Udat::Collection#fetch,
30
+ # when a fetched value is a collection instead of a scalar or vice versa.
31
+ class UdatTypeMismatch < StandardError; end
32
+ # UDAT parsing error
33
+ class ParseError < StandardError; end
30
34
 
31
- # Special argument passed to Udat#to_udat, if the tag is to be kept.
35
+ # Special argument passed to Udat::Node#to_udat, if the tag is to be
36
+ # kept.
32
37
  KeepTag = Object.new
33
38
 
34
39
  # A string containing an UDAT example document.
35
40
  ExampleDocument = <<-'END_OF_EXAMPLE'
36
- This file contains several UDAT objects (each of them enclosed in
37
- square brackets), serving as an example for the UDAT format:
38
-
39
-
40
- Scalar values:
41
-
42
- [Hello World!]
43
-
44
- [UTF-8|Hello World!] This object has a tag "UTF-8".
45
- Intepretation of tags is task of the
46
- application, there are no standard tags
47
- defined.
48
-
49
- [23]
50
-
51
- [integer|23]
52
-
53
- [rational|2/3]
54
-
55
- The following 7 characters must be escaped with a backslash, when
56
- being part of a tag or being part of the content of a scalar:
57
-
58
- \< \> \[ \] \| \~ \\
59
-
60
-
61
- Arrays (or sets):
62
-
63
- [ [1] [2] [3] ]
64
-
65
- [ [1] [2] Comments are allowed here! (and alomost everywhere) [3] ]
66
-
67
- [ [person| <firstname>[Max] <lastname>[Mustermann] ]
68
- [person| <firstname>[Martin] <lastname>[html|M&uuml;ller] ]
69
- [group| <name>[Sample group] <members> [
70
- [person| <firstname>[Max] <lastname>[Mustermann] ]
71
- [person| <firstname>[Martin] <lastname>[html|M&uuml;ller] ]
72
- ] ] ]
73
-
74
- References like in YAML with &id and *id
75
- are not directly supported by UDAT.
76
-
77
- Empty arrays, sets or maps must contain the ~ character, to avoid
78
- ambiguity with scalars:
79
-
80
- [ ] scalar containing three spaces
81
- [ ~ ] empty array
82
- [list of numbers| ~ ] empty array, tagged with "list of numbers"
83
- [ ~ comment ] empty array with a comment
84
- [ comment ~ ] another empty array with a comment
85
- [ ~~ ] empty array too (multiple ~ are allowed)
86
-
87
- It is also allowed to write ~ in non-empty arrays. In that case the ~
88
- symbol is ignored.
89
-
90
- [~ [1] [2] [3] ] Array containing "1", "2" and "3"
91
-
92
-
93
- Ordered or unordered maps:
94
-
95
- [ <&> [html|&amp;]
96
- <"> [html|&quot;]
97
- <\<> [html|&lt;]
98
- <\>> [html|&gt;] ]
99
-
100
- [ <color combination|[red][blue]> [nice]
101
- <color combination|[red][magenta]> [ugly] ]
102
-
103
- [ < <red>[on] <yellow>[off] <green>[off] > [stop]
104
- < <red>[on] <yellow>[on] <green>[off] > [attention]
105
- < <red>[off] <yellow>[off] <green>[on] > [go]
106
- < <red>[off] <yellow>[on] <green>[off] > [prepare to stop] ]
107
-
108
-
109
- Sample configuration file:
110
-
111
- [sample config v1.0|
112
- <locale> [
113
- <language> [de]
114
- <timezone> [CET]
115
- ]
116
- <logging> [
117
- <verbosity> [high]
118
- <destination> [file|/var/log/sample.log]
119
- ]
120
- <network> [
121
- <max connections> [256]
122
- <reverse lookups> [yes]
123
- ]
124
- <access control> [
125
- <allowed> [ [user|martin] [user|max] [group|admins] ]
41
+ [sample config v1.0|
42
+
43
+ <network> [
44
+ <max connections> [256]
45
+ <reverse lookups> [yes]
46
+ ]
47
+
48
+ <locale> [
49
+ <language> [de] german language
50
+ <timezone> [CET] and CET timezone
51
+ Comments can appear almost anywhere.
52
+ ]
53
+
54
+ <access control> [
55
+ <allowed> [
56
+ [user|martin]
57
+ [user|max]
58
+ [group|admins]
59
+ [ip4network|
60
+ <address> [192.168.0.0]
61
+ <netmask> [255.255.255.0]
126
62
  ]
127
63
  ]
64
+ <blocked> [~] The tilde symbol denotes an empty collection.
65
+ ]
128
66
 
67
+ <misc> [
68
+ <symbol for homedir> [\~] Not an empty collection but the scalar
69
+ value "~".
70
+ ]
129
71
 
130
- Don't abuse tags to store complicated meta information like this:
72
+ <address mappings> [
131
73
 
132
- WRONG! [string charset=ISO-8859-1 language=EN|Hello World!] WRONG!
74
+ <email|
75
+ <user> [jan.behrens]
76
+ <domain> [flexiguided.de]
77
+ >
78
+ [mbox|jan]
133
79
 
80
+ <email|
81
+ <user> [glob|*]
82
+ <domain> [flexiguided.de]
83
+ >
84
+ [mbox|catchall]
134
85
 
135
- If you need to store more complicated meta information, you should do
136
- that by using ordinary key/value pairs:
137
-
138
- [string with metainfo v1.0|
139
- <charset> [ISO-8859-1]
140
- <language> [en]
141
- <content> [Hello World!]
142
- ]
86
+ ]
143
87
 
88
+ <logging> [
89
+ <verbosity> [high]
90
+ \## <verbosity> [debug] ## Uncomment this for debug output.
91
+ <destination> [file|/var/log/sample.log]
92
+ ]
144
93
 
94
+ ]
145
95
  END_OF_EXAMPLE
146
96
 
147
- # Error raised by UdatCollection#fetch,
148
- # when a fetched value doesn't have the expected tag (i.e. type).
149
- class UdatTagMismatch < StandardError; end
150
- # Error raised by UdatCollection#fetch,
151
- # when a fetched value is a collection instead of a scalar or vice versa.
152
- class UdatTypeMismatch < StandardError; end
153
- # UDAT parsing error
154
- class UdatParseError < StandardError; end
155
-
156
- private_class_method :new
157
- # Creates a new Udat object with a given tag (which may be nil).
158
- # This method is private,
159
- # Udat#initialize is only called through supercalls.
160
- def initialize(tag)
161
- super()
162
- self.tag = tag
163
- end
164
-
165
- # Returns an escaped version of a string, where backslashes are preceding
166
- # certain reserved characters.
167
- def self.escape_string(string)
168
- string.to_s.gsub /([<>\[\]|~\\])/, "\\\\\\1"
169
- end
170
- # Calls Udat.escape_string.
97
+ # Returns an escaped version of a string, where backslashes are
98
+ # preceding certain reserved characters.
171
99
  def escape_string(string)
172
- self.class.escape_string(string)
173
- end
174
- private :escape_string
175
-
176
- # Tag (i.e. type of the content).
177
- attr_reader :tag
178
- # Sets the tag (i.e. type of the content).
179
- def tag=(tag)
180
- synchronize do
181
- @tag = tag ? tag.to_s.dup.freeze : nil
182
- end
183
- return tag
184
- end
185
-
186
- # Returns true, if the UDAT object represents a scalar value.
187
- def scalar?
188
- kind_of? UdatScalar
189
- end
190
- # Returns true, if the UDAT object represents a collection.
191
- def collection?
192
- kind_of? UdatCollection
100
+ string.to_s.gsub /([<>\[\]|~\\])/, "\\\\\\1"
193
101
  end
102
+ module_function :escape_string
194
103
 
195
- # Returns the data (including it's tag) encoded in the UDAT format.
104
+ # The abstract class to represent any UDAT data is a Udat::Node.
105
+ # Udat::Node's basically consist of a tag and the content. The tag can be
106
+ # a String or be nil. The content is either a scalar value stored as a
107
+ # String, or an ordered/unordered collection of values (where each value
108
+ # can optionally have a key associated with it). Keys and values of
109
+ # collections are always Udat::Node's too.
196
110
  #
197
- # Note: The UDAT format doesn't contain information, whether contained
198
- # collections are ordered or unordered. This information is lost during
199
- # the encoding process, and has to be restored in an application
200
- # specific way, if neccessary.
201
- def encode
202
- synchronize do
203
- if tag
204
- return "#{escape_string tag}|#{encoded_content}"
205
- else
206
- return encoded_content
207
- end
111
+ # Udat::Node's can most easily be constructed from other ruby objects by
112
+ # using the Object#to_udat method.
113
+ #
114
+ # By calling the method Udat::Node#encode_document, the Node and its
115
+ # children are encoded in a both easily human readable and easily machine
116
+ # readable format. The output can be later parsed by
117
+ # String#parse_udat_document or IO#read_udat.
118
+ class Node
119
+
120
+ include MonitorMixin
121
+
122
+ private_class_method :new
123
+ # Creates a new Udat::Node with a given tag (which may be nil). This
124
+ # method is private, Udat::Node#initialize is only called through
125
+ # supercalls.
126
+ def initialize(tag)
127
+ super()
128
+ self.tag = tag
208
129
  end
209
- end
210
- # Here the method does the same as Udat#encode, but this method is
211
- # overwritten in UdatScalar!
212
- def to_s
213
- encode
214
- end
215
- # Does the same as Udat#encode, but encloses the results in curly
216
- # brackets and preceds them with the string "udat".
217
- def inspect
218
- "udat{#{self.encode}}"
219
- end
220
130
 
221
- # Returns self, or a duplicate of self with a different tag set, if an
222
- # argument is passed.
223
- def to_udat(tag = KeepTag)
224
- if tag == KeepTag
225
- return self
226
- else
227
- obj = nil
131
+ # Tag (i.e. type of the content).
132
+ attr_reader :tag
133
+ # Sets the tag (i.e. type of the content).
134
+ def tag=(tag)
228
135
  synchronize do
229
- obj = self.dup
136
+ @tag = tag ? tag.to_s.dup.freeze : nil
230
137
  end
231
- obj.tag = tag
232
- return obj
138
+ return tag
233
139
  end
234
- end
235
140
 
236
- # Returns a hash key used by ruby's Hash'es.
237
- def hash
238
- to_s.hash
239
- end
240
- # Returns true, if class, tag and content (including it's order in case
241
- # of a UdatCollection) are matching another object.
242
- def eql?(other)
243
- self.class == other.class and
244
- self.tag == other.tag and
245
- self.to_s == other.to_s
246
- end
247
- # Same as Udat#eql?, but behaves differently in UdatCollection.
248
- def ==(other)
249
- self.eql? other
250
- end
141
+ # Returns true, if the Udat::Node represents a scalar value.
142
+ # Shortcut for Udat::Node#kind_of? Udat::Scalar.
143
+ def scalar?
144
+ kind_of? Scalar
145
+ end
146
+ # Returns true, if the Udat::Node represents a collection.
147
+ # Shortcut for Udat::Node#kind_of? Udat::Collection.
148
+ def collection?
149
+ kind_of? Collection
150
+ end
251
151
 
252
- # Internal parsing function.
253
- def self.parse_intern(input, start_pos = 0)
254
- string = ""
255
- tag = nil
256
- key = nil
257
- collection = nil
258
- pos = start_pos
259
- while pos < input.length
260
- char = input[pos, 1]
261
- case char
262
- when "\\"
263
- pos += 1
264
- char = input[pos, 1]
265
- if char.empty?
266
- raise UdatParseError, "Backslash at end of input."
267
- end
268
- if char =~ /([<>\[\]|~\\])/
269
- string << char if string
270
- elsif char == "\n"
271
- elsif char == "\r"
272
- next_char = input[pos + 1, 1]
273
- pos += 1 if next_char == "\n"
274
- else
275
- raise UdatParseError, "Unknown escape sequence found."
276
- end
277
- pos += 1
278
- when "|"
152
+ # Returns the data (including it's tag) encoded in the UDAT format. The
153
+ # result is not enclosed by square brackets, so not intended to be
154
+ # used externally. This method is public for backwards compatiblity
155
+ # only, and will be declared protected in future.
156
+ # Use Udat::Node#encode_document instead.
157
+ def encode_part
158
+ synchronize do
279
159
  if tag
280
- raise UdatParseError, "Multiple occurrences of pipe symbol."
281
- end
282
- unless string
283
- raise UdatParseError, "Unexpected occurrence of pipe symbol."
284
- end
285
- tag = string
286
- string = ""
287
- pos += 1
288
- when "~"
289
- collection ||= UdatCollection.new(tag, [])
290
- string = nil
291
- pos += 1
292
- when "<"
293
- if key
294
- raise UdatParseError, "Unexpected opening angle bracket."
295
- end
296
- key, pos = parse_intern(input, pos + 1)
297
- unless input[pos, 1] == ">"
298
- raise ArgumentError, "Closing angle bracket not found."
299
- end
300
- pos += 1
301
- when ">"
302
- break
303
- when "["
304
- value, pos = parse_intern(input, pos + 1)
305
- unless input[pos, 1] == "]"
306
- raise UdatParseError, "Closing square bracket not found."
307
- end
308
- collection ||= UdatCollection.new(tag, [])
309
- if key
310
- collection.append_pair(key, value)
311
- key = nil
160
+ return "#{Udat::escape_string tag}|#{encoded_content}"
312
161
  else
313
- collection.append_value(value)
162
+ return encoded_content
314
163
  end
315
- pos += 1
316
- when "]"
317
- break
164
+ end
165
+ end
166
+ # Returns the data (including it's tag) encoded in the UDAT format and
167
+ # enclosed in square brackets. This is the preferred form of encoding,
168
+ # when storing the data in files or over streams.
169
+ #
170
+ # Note: The UDAT format doesn't contain information, whether contained
171
+ # collections are ordered or unordered. This information is lost during
172
+ # the encoding process, and has to be restored in an application
173
+ # specific way, if neccessary.
174
+ def encode_document
175
+ "[#{encode_part}]"
176
+ end
177
+ # Deprecated. This method is an alias for Udat::Node#encode_part, but
178
+ # it is planned to be changed to Udat::Node#encode_document.
179
+ def encode
180
+ encode_part
181
+ end
182
+ # Here the method does the same as Udat::Node#encode_part, but this
183
+ # method is overwritten in Udat::Scalar! It is also planned to change
184
+ # the behaviour to Udat::Node#encode_document in future.
185
+ def to_s
186
+ encode_part
187
+ end
188
+ # Does the same as Udat::Node#encode_document, but preceds the result
189
+ # with "udat".
190
+ def inspect
191
+ "udat#{self.encode_document}"
192
+ end
193
+
194
+ # Returns self, or a duplicate of self with a different tag set, if an
195
+ # argument is passed.
196
+ def to_udat(tag = KeepTag)
197
+ if tag == KeepTag
198
+ return self
318
199
  else
319
- string << char if string
320
- pos += 1
200
+ obj = nil
201
+ synchronize do
202
+ obj = self.dup
203
+ end
204
+ obj.tag = tag
205
+ return obj
321
206
  end
322
207
  end
323
- raise UdatParseError, "Key without value." if key
324
- return (collection ? collection : string.to_udat(tag)), pos
325
- end
326
- private_class_method :parse_intern
327
208
 
328
- # Parses a given string and returns a structure of Udat objects.
329
- #
330
- # Note: When parsing UDAT data, no information is gained, whether
331
- # collections are ordered or unordered. After parsing, all collections
332
- # will be marked as unordered, unless changed later by the application.
333
- def self.parse(input)
334
- input = input.to_s
335
- result, pos = parse_intern(input)
336
- if pos < input.length
337
- raise UdatParseError, "Closing bracket without opening bracket."
338
- end
339
- return result
340
- end
209
+ # Returns a hash key used by ruby's Hash'es.
210
+ def hash
211
+ to_s.hash
212
+ end
213
+ # Returns true, if class, tag and content (including it's order in case
214
+ # of a Udat::Collection) are matching another object.
215
+ def eql?(other)
216
+ self.class == other.class and
217
+ self.tag == other.tag and
218
+ self.to_s == other.to_s
219
+ end
220
+ # Same as Udat::Node#eql?, but behaves differently in Udat::Collection.
221
+ def ==(other)
222
+ self.eql? other
223
+ end
341
224
 
342
- # Reads an encoded UDAT object from a stream.
343
- # The object must be enclosed in square brackets.
344
- def self.read_from_stream(io)
345
- begin
346
- begin
347
- char = io.read(1)
348
- return nil if char.nil? or char.length < 1
349
- if char == "\\"
350
- char = io.read(1)
351
- return nil if char.nil? or char.length < 1
352
- end
353
- end while char != "[" and char != "<"
354
- buffer = (char == "[") ? "" : nil
355
- level = 1
225
+ # Internal parsing function.
226
+ def self.parse_intern(mode, end_char, &input_reader)
227
+ string = ""
228
+ tag = nil
229
+ collection = nil
230
+ key = nil
231
+ redo_char = false
356
232
  while true
357
- char = io.read(1)
358
- if char.nil? or char.length < 1
359
- return EOFError, "Unexpected end of file in UDAT stream."
233
+ if redo_char
234
+ redo_char = false
235
+ else
236
+ char = input_reader.call
360
237
  end
361
- if char == "\\"
362
- buffer << char if buffer
363
- char = io.read(1)
364
- if char.nil? or char.length < 1
365
- return EOFError, "Unexpected end of file in UDAT stream."
238
+ case char
239
+ when nil
240
+ if end_char.nil? and not key
241
+ break
242
+ else
243
+ raise EOFError, "Unexpected end of UDAT input."
244
+ end
245
+ when "\\"
246
+ char = input_reader.call
247
+ if char.nil?
248
+ raise EOFError, "Unexpected end of UDAT input."
249
+ end
250
+ if char =~ /([<>\[\]|~\\])/
251
+ string << char if string
252
+ elsif char == "\n"
253
+ elsif char == "\r"
254
+ char = input_reader.call
255
+ unless char == "\n"
256
+ redo_char = true
257
+ redo
258
+ end
259
+ elsif char == "#"
260
+ boundary = ""
261
+ while true
262
+ char = input_reader.call
263
+ if char.nil?
264
+ raise EOFError, "Unexpected end of UDAT input."
265
+ end
266
+ if char == "#"
267
+ break
268
+ else
269
+ boundary << char
270
+ end
271
+ end
272
+ while true
273
+ char = input_reader.call
274
+ if char.nil?
275
+ raise EOFError,
276
+ "Unexpected end of UDAT input " <<
277
+ "before ending boundary."
278
+ end
279
+ if char == "#"
280
+ boundary_end = ""
281
+ while true
282
+ char = input_reader.call
283
+ if char.nil?
284
+ raise EOFError,
285
+ "Unexpected end of UDAT input " <<
286
+ "before ending boundary."
287
+ end
288
+ if char == "#"
289
+ if boundary_end == boundary
290
+ break
291
+ else
292
+ string << "#" << boundary_end if string
293
+ boundary_end = ""
294
+ redo
295
+ end
296
+ else
297
+ boundary_end << char
298
+ end
299
+ end
300
+ break
301
+ else
302
+ string << char if string
303
+ end
304
+ end
305
+ elsif char == "$"
306
+ length = 0
307
+ while true
308
+ char = input_reader.call
309
+ if char.nil?
310
+ raise EOFError, "Unexpected end of UDAT input."
311
+ elsif char =~ /[0-9]/
312
+ length *= 10
313
+ length += char.to_i
314
+ elsif char == "$"
315
+ break
316
+ else
317
+ raise ParseError,
318
+ "Illegal character in length information of UDAT input."
319
+ end
320
+ end
321
+ length.times do
322
+ char = input_reader.call
323
+ if char.nil?
324
+ raise EOFError,
325
+ "Unexpected end of UDAT input in binary part."
326
+ end
327
+ string << char if string
328
+ end
329
+ else
330
+ raise ParseError, "Unknown escape sequence in UDAT input."
331
+ end
332
+ when "|"
333
+ if tag or not string
334
+ raise ParseError, "Unexpected pipe symbol in UDAT input."
335
+ end
336
+ tag = string
337
+ string = ""
338
+ when "~"
339
+ collection ||= Collection.new(tag, [])
340
+ string = nil
341
+ when "<"
342
+ if key
343
+ raise ParseError,
344
+ "Unexpected opening angle bracket in UDAT input."
345
+ end
346
+ key = parse_intern(:normal, ">", &input_reader)
347
+ string = nil
348
+ when ">"
349
+ if end_char == ">" and not key
350
+ break
351
+ else
352
+ raise ParseError,
353
+ "Unexpected closing angle bracket in UDAT input."
354
+ end
355
+ when "["
356
+ value = parse_intern(:normal, "]", &input_reader)
357
+ return value if mode == :one_value
358
+ collection ||= Collection.new(tag, [])
359
+ if key
360
+ collection.append_pair(key, value)
361
+ key = nil
362
+ else
363
+ collection.append_value(value)
364
+ end
365
+ string = nil
366
+ when "]"
367
+ if end_char == "]" and not key
368
+ break
369
+ else
370
+ raise ParseError,
371
+ "Unexpected closing square bracket in UDAT input."
366
372
  end
367
- elsif char == "[" or char == "<"
368
- level += 1
369
- elsif char == "]" or char == ">"
370
- level -= 1
371
- end
372
- if level > 0
373
- buffer << char if buffer
374
373
  else
375
- break
374
+ string << char if string
376
375
  end
377
376
  end
378
- end while buffer.nil?
379
- return parse(buffer)
380
- end
377
+ return nil if mode == :one_value
378
+ return collection ? collection : string.to_udat(tag)
379
+ end
380
+ private_class_method :parse_intern
381
+
382
+ # Deprecated. Parses a string encoded by Udat::Node#encode_part. This
383
+ # method might be removed in future versions. Use
384
+ # Udat::Node#encode_document in combination with
385
+ # Udat::Node.parse_document instead.
386
+ def self.parse_part(input)
387
+ input = input.to_s
388
+ pos = 0
389
+ begin
390
+ return (
391
+ parse_intern(:normal, nil) do
392
+ char = input[pos, 1]
393
+ pos += 1
394
+ next char.empty? ? nil : char
395
+ end
396
+ )
397
+ rescue EOFError
398
+ raise ParseError, $!.message
399
+ end
400
+ end
401
+ # Parses a given UDAT document string and returns a structure of
402
+ # Udat::Node's. It does the same as Udat::Node.parse_part(input).first.
403
+ #
404
+ # Note: When parsing UDAT data, no information is gained, whether
405
+ # collections are ordered or unordered. After parsing, all collections
406
+ # will be marked as unordered, unless changed later by the application.
407
+ def self.parse_document(input)
408
+ parse(input).first or raise ParseError, "No UDAT object found."
409
+ end
410
+ # Deprecated. This method is an alias for Udat::Node.parse_part, but
411
+ # in future it might be changed to Udat::Node.parse_document.
412
+ def self.parse(input)
413
+ parse_part(input)
414
+ end
381
415
 
382
- # Encodes an UDAT object and writes it enclosed by square brackets to a
383
- # stream.
384
- def write_to_stream(io)
385
- io << "[#{self.to_udat}]"
386
- return self
387
- end
416
+ # Reads Udat::Node encoded by Udat::Node#encode_document from a stream.
417
+ def self.read_from_stream(io)
418
+ return (
419
+ parse_intern(:one_value, nil) do
420
+ io.read(1)
421
+ end
422
+ )
423
+ end
388
424
 
389
- end
425
+ # Encodes an object by calling Object#to_udat followed by
426
+ # Udat::Node#encode_document, and writes it to a stream. Returns self.
427
+ def write_to_stream(io)
428
+ io << self.to_udat.encode_document
429
+ return self
430
+ end
390
431
 
432
+ end
391
433
 
392
- # Class of UDAT objects holding an ordered or unordered collection of
393
- # values (where each value can optionally have a key associated with it).
394
- # Keys and values of collections are always UDAT objects too.
395
- # UdatCollection's can be interpreted as maps, arrays, sets or any other
396
- # non-scalar data structure.
397
- #
398
- # Note: Keys and values are always implicitly casted by all instance
399
- # methods of this class to Udat objects using Object#to_udat.
400
- class UdatCollection < Udat
401
434
 
402
- # Internally used wrapper class for Udat objects, to lookup
403
- # UdatCollection's in Hash'es, while ignoring the order of their content.
404
- class UdatUnorderedWrapper
405
- def initialize(content)
406
- @content = content
407
- end
408
- attr_accessor :content
409
- def hash
410
- if content.kind_of? UdatCollection
411
- hash = 0
412
- content.key_value_pairs.each do |key, value|
413
- hash += key.hash
414
- hash += value.hash
435
+ # Class of Udat::Node's holding an ordered or unordered collection of
436
+ # values (where each value can optionally have a key associated with it).
437
+ # Keys and values of collections are always Udat::Node's too.
438
+ # Udat::Collection's can be interpreted as maps, arrays, sets or any
439
+ # other non-scalar data structure.
440
+ #
441
+ # Note: Keys and values are always implicitly casted by all instance
442
+ # methods of this class to Udat::Node's using Object#to_udat.
443
+ class Collection < Node
444
+
445
+ # Internally used wrapper class for Udat::Node's, to lookup
446
+ # Udat::Collection's in Hash'es, while ignoring the order of their
447
+ # content.
448
+ class UnorderedWrapper
449
+ def initialize(content)
450
+ @content = content
451
+ end
452
+ attr_accessor :content
453
+ def hash
454
+ if content.kind_of? Collection
455
+ hash = 0
456
+ content.key_value_pairs.each do |key, value|
457
+ hash += key.hash
458
+ hash += value.hash
459
+ end
460
+ return hash
461
+ else
462
+ return content.hash
415
463
  end
416
- return hash
417
- else
418
- return content.hash
419
464
  end
420
- end
421
- def eql?(other)
422
- return false unless self.class == other.class
423
- if self.content.class == other.content.class and
424
- self.content.tag == other.content.tag
425
- if self.content.kind_of? UdatCollection
426
- own_pairs = self.content.key_value_pairs
427
- other_pairs = other.content.key_value_pairs
428
- own_pairs.each do |own_pair|
429
- catch :found do
430
- other_pairs.each_index do |index|
431
- if own_pair.eql? other_pairs[index]
432
- other_pairs.delete_at index
433
- throw :found
465
+ def eql?(other)
466
+ return false unless self.class == other.class
467
+ unless self.content.kind_of? Node and
468
+ other.content.kind_of? Node
469
+ return self.content.eql?(other.content)
470
+ end
471
+ if self.content.class == other.content.class
472
+ self.content.tag == other.content.tag
473
+ if self.content.kind_of? Collection
474
+ own_pairs = self.content.key_value_pairs
475
+ other_pairs = other.content.key_value_pairs
476
+ own_pairs.each do |own_pair|
477
+ catch :found do
478
+ other_pairs.each_index do |index|
479
+ if own_pair.eql? other_pairs[index]
480
+ other_pairs.delete_at index
481
+ throw :found
482
+ end
434
483
  end
484
+ return false
435
485
  end
436
- return false
437
486
  end
487
+ return true
488
+ else
489
+ return self.content.eql?(other.content)
438
490
  end
439
- return true
440
491
  else
441
- return self.content.eql?(other.content)
492
+ return false
442
493
  end
443
- else
444
- return false
494
+ end
495
+ def ==(other)
496
+ self.eql? other
445
497
  end
446
498
  end
447
- def ==(other)
448
- self.eql? other
449
- end
450
- end
451
499
 
452
- public_class_method :new
453
- # Creates a new Udat object with a given tag (which may be nil) and a
454
- # given content, represented by a nested Array structure. See the source
455
- # code for details. It is not recommended to use this method. Use
456
- # Object#to_udat instead.
457
- def initialize(tag, content)
458
- super tag
459
- @ordered = true
460
- @entries = []
461
- require_index_hashes
462
- content.to_ary.each { |entry| self << entry }
463
- end
500
+ public_class_method :new
501
+ # Creates a new Udat::Collection with a given tag (which may be nil)
502
+ # and a given content, represented by a nested Array structure. It is
503
+ # not recommended to use this method. Use Object#to_udat instead.
504
+ def initialize(tag, content)
505
+ super tag
506
+ @ordered = true
507
+ @entries = []
508
+ require_index_hashes
509
+ content.to_ary.each { |entry| self << entry }
510
+ end
464
511
 
465
- # Deletes the internal index hashes.
466
- # They are automatically reconstructed once a lookup is done.
467
- # This method should be called by the user, if contained Udat objects
468
- # have changed, AFTER being added to this collection (similar to
469
- # Hash#rehash).
470
- def rehash
471
- synchronize do
472
- @key_indicies = nil
473
- @value_indicies = nil
474
- @unordered_key_indicies = nil
475
- @unordered_value_indicies = nil
476
- @all_unordered_key_indicies = nil
477
- @all_unordered_value_indicies = nil
512
+ # Deletes the internal index hashes.
513
+ # They are automatically reconstructed once a lookup is done.
514
+ # This method should be called by the user, if contained Udat objects
515
+ # have changed, AFTER being added to this collection (similar to
516
+ # Hash#rehash).
517
+ # Returns self.
518
+ def rehash
519
+ synchronize do
520
+ @key_indicies = nil
521
+ @value_indicies = nil
522
+ @unordered_key_indicies = nil
523
+ @unordered_value_indicies = nil
524
+ @all_unordered_key_indicies = nil
525
+ @all_unordered_value_indicies = nil
526
+ end
527
+ return self
478
528
  end
479
- return self
480
- end
481
529
 
482
- # Returns true, if the internal index hashes are not existent.
483
- def index_hashes_void?
484
- synchronize do
485
- return @key_indicies.nil?
530
+ # Returns true, if the internal index hashes are not existent.
531
+ def index_hashes_void?
532
+ synchronize do
533
+ return @key_indicies.nil?
534
+ end
486
535
  end
487
- end
488
- private :index_hashes_void?
489
- # Registers a key value pair in the index.
490
- def add_to_index_hashes(index, key, value)
491
- @key_indicies[key] ||= []
492
- @key_indicies[key] << index
493
- @value_indicies[value] ||= []
494
- @value_indicies[value] << index
495
- if key.kind_of? UdatCollection and key.unordered?
496
- @unordered_key_indicies[UdatUnorderedWrapper.new(key)] ||= []
497
- @unordered_key_indicies[UdatUnorderedWrapper.new(key)] << index
498
- end
499
- if value.kind_of? UdatCollection and value.unordered?
500
- @unordered_value_indicies[UdatUnorderedWrapper.new(value)] ||= []
501
- @unordered_value_indicies[UdatUnorderedWrapper.new(value)] << index
502
- end
503
- @all_unordered_key_indicies[UdatUnorderedWrapper.new(key)] ||= []
504
- @all_unordered_key_indicies[UdatUnorderedWrapper.new(key)] << index
505
- @all_unordered_value_indicies[UdatUnorderedWrapper.new(value)] ||= []
506
- @all_unordered_value_indicies[UdatUnorderedWrapper.new(value)] << index
507
- return self
508
- end
509
- private :add_to_index_hashes
510
- # Creates index hashes, if they are not existent.
511
- def require_index_hashes
512
- synchronize do
513
- if index_hashes_void?
514
- @key_indicies = {}
515
- @value_indicies = {}
516
- @unordered_key_indicies = {}
517
- @unordered_value_indicies = {}
518
- @all_unordered_key_indicies = {}
519
- @all_unordered_value_indicies = {}
520
- @entries.each_index do |index|
521
- key_value_pair = @entries[index]
522
- key = key_value_pair[0]
523
- value = key_value_pair[1]
524
- add_to_index_hashes(index, key, value) unless key.nil?
536
+ private :index_hashes_void?
537
+ # Registers a key value pair in the index.
538
+ # (The key argument may be nil for values without keys.)
539
+ def add_to_index_hashes(index, key, value)
540
+ unless index_hashes_void?
541
+ @key_indicies[key] ||= []
542
+ @key_indicies[key] << index
543
+ @value_indicies[value] ||= []
544
+ @value_indicies[value] << index
545
+ if key.kind_of? Collection and key.unordered?
546
+ @unordered_key_indicies[UnorderedWrapper.new(key)] ||= []
547
+ @unordered_key_indicies[UnorderedWrapper.new(key)] << index
548
+ end
549
+ if value.kind_of? Collection and value.unordered?
550
+ @unordered_value_indicies[UnorderedWrapper.new(value)] ||= []
551
+ @unordered_value_indicies[UnorderedWrapper.new(value)] << index
525
552
  end
553
+ @all_unordered_key_indicies[UnorderedWrapper.new(key)] ||= []
554
+ @all_unordered_key_indicies[UnorderedWrapper.new(key)] << index
555
+ @all_unordered_value_indicies[UnorderedWrapper.new(value)] ||= []
556
+ @all_unordered_value_indicies[UnorderedWrapper.new(value)] << index
526
557
  end
558
+ return self
527
559
  end
528
- return self
529
- end
530
- private :require_index_hashes
560
+ private :add_to_index_hashes
561
+ # Creates index hashes, if they are not existent.
562
+ def require_index_hashes
563
+ synchronize do
564
+ if index_hashes_void?
565
+ @key_indicies = {}
566
+ @value_indicies = {}
567
+ @unordered_key_indicies = {}
568
+ @unordered_value_indicies = {}
569
+ @all_unordered_key_indicies = {}
570
+ @all_unordered_value_indicies = {}
571
+ @entries.each_index do |index|
572
+ key_value_pair = @entries[index]
573
+ key = key_value_pair[0]
574
+ value = key_value_pair[1]
575
+ add_to_index_hashes(index, key, value) unless key.nil?
576
+ end
577
+ end
578
+ end
579
+ return self
580
+ end
581
+ private :require_index_hashes
531
582
 
532
- # Empties the collection.
533
- def clear
534
- synchronize do
535
- @entries.clear
536
- rehash
537
- require_index_hashes
583
+ # Empties the collection. Returns self.
584
+ def clear
585
+ synchronize do
586
+ @entries.clear
587
+ rehash
588
+ require_index_hashes
589
+ end
590
+ return self
538
591
  end
539
- return self
540
- end
541
- # Deletes the entry at the given numeric index.
542
- # Returns the value, or nil if the index is out of bounds.
543
- def delete_at(index)
544
- index = index.to_int
545
- synchronize do
546
- key_value_pair = @entries.delete_at(index)
547
- rehash
548
- return key_value_pair ? key_value_pair[1] : nil
592
+ # Deletes the entry at the given numeric index.
593
+ # Returns the value, or nil if the index is out of bounds.
594
+ def delete_at(index)
595
+ index = index.to_int
596
+ synchronize do
597
+ key_value_pair = @entries.delete_at(index)
598
+ rehash
599
+ return key_value_pair ? key_value_pair[1] : nil
600
+ end
549
601
  end
550
- end
551
- # Deletes the first entry and returns its value.
552
- def shift
553
- delete_at(0)
554
- end
555
- # Deletes the last entry and returns its value.
556
- def pop
557
- delete_at(-1)
558
- end
559
- # Deletes all entries with a given key.
560
- def delete_key(key)
561
- key = key.to_udat
562
- synchronize do
563
- @entries.delete_if { |e_key, e_value| e_key == key }
564
- rehash
602
+ # Deletes the first entry and returns its value.
603
+ def shift
604
+ delete_at(0)
565
605
  end
566
- return self
567
- end
568
- # Deletes all entries with a given value.
569
- def delete_value(value)
570
- value = value.to_udat
571
- synchronize do
572
- @entries.delete_if { |e_key, e_value| e_value == value }
573
- rehash
606
+ # Deletes the last entry and returns its value.
607
+ def pop
608
+ delete_at(-1)
609
+ end
610
+ # Deletes all entries with a given key. Returns self.
611
+ def delete_key(key)
612
+ key = key.to_udat
613
+ synchronize do
614
+ @entries.delete_if { |e_key, e_value| e_key == key }
615
+ rehash
616
+ end
617
+ return self
618
+ end
619
+ # Deletes all entries with a given value. Returns self.
620
+ def delete_value(value)
621
+ value = value.to_udat
622
+ synchronize do
623
+ @entries.delete_if { |e_key, e_value| e_value == value }
624
+ rehash
625
+ end
626
+ return self
574
627
  end
575
- return self
576
- end
577
628
 
578
- # Appends a new key with a new value.
579
- def append_pair(key, value)
580
- key = key.to_udat
581
- value = value.to_udat
582
- synchronize do
583
- index = @entries.length
584
- @entries << [key, value]
585
- add_to_index_hashes(index, key, value) unless index_hashes_void?
629
+ # Appends a new key with a new value. Returns self.
630
+ def append_pair(key, value)
631
+ key = key.to_udat
632
+ value = value.to_udat
633
+ synchronize do
634
+ index = @entries.length
635
+ @entries << [key, value]
636
+ add_to_index_hashes(index, key, value)
637
+ end
638
+ return self
586
639
  end
587
- return self
588
- end
589
- # Appends a new value.
590
- def append_value(value)
591
- value = value.to_udat
592
- synchronize do
593
- @entries << [nil, value]
640
+ # Appends a new value. Returns self.
641
+ def append_value(value)
642
+ value = value.to_udat
643
+ synchronize do
644
+ index = @entries.length
645
+ @entries << [nil, value]
646
+ add_to_index_hashes(index, nil, value)
647
+ end
648
+ return self
649
+ end
650
+ # Deletes all key/value pairs where the given key matches,
651
+ # and appends a new key/value pair. Returns self.
652
+ def set_value_for_key(key, value)
653
+ delete_key(key)
654
+ append_pair(key, value)
655
+ return self
656
+ end
657
+ # Deletes all values or key/value pairs where the given value matches,
658
+ # and appends a new key/value pair. Returns self.
659
+ def set_key_for_value(key, value)
660
+ delete_value(value)
661
+ append_pair(key, value)
662
+ return self
594
663
  end
595
- return self
596
- end
597
- # Deletes all key/value pairs where the given key matches,
598
- # and appends a new key/value pair.
599
- def set_value_for_key(key, value)
600
- delete_key(key)
601
- append_pair(key, value)
602
- return self
603
- end
604
- # Deletes all values or key/value pairs where the given value matches,
605
- # and appends a new key/value pair.
606
- def set_key_for_value(key, value)
607
- delete_value(value)
608
- append_pair(key, value)
609
- return self
610
- end
611
664
 
612
- # Behaves differently depending on the type of the argument. If the
613
- # argument is an Array with 2 elements, a new key/value pair is added,
614
- # with the key being the first element of the array, and the value being
615
- # the second argument of the array. Arrays with any other number of
616
- # elements cause an error. If the argument is not an array, then it is
617
- # added as a value without a key.
618
- def <<(value)
619
- if value.kind_of? Array
620
- unless value.length == 2
621
- raise ArgumentError,
622
- "#{value.length} array elements found where 2 were expected."
623
- end
624
- return append_pair(value[0], value[1])
625
- else
626
- return append_value(value)
665
+ # Behaves differently depending on the type of the argument. If the
666
+ # argument is an Array with 2 elements, a new key/value pair is added,
667
+ # with the key being the first element of the array, and the value
668
+ # being the second argument of the array. Arrays with any other number
669
+ # of elements cause an error. If the argument is not an array, then it
670
+ # is added as a value without a key.
671
+ def <<(value)
672
+ if value.kind_of? Array
673
+ unless value.length == 2
674
+ raise ArgumentError,
675
+ "#{value.length} array elements found where 2 were expected."
676
+ end
677
+ return append_pair(value[0], value[1])
678
+ else
679
+ return append_value(value)
680
+ end
681
+ end
682
+ # Same as Udat::Collection#set_value_for_key.
683
+ def []=(key, value)
684
+ set_value_for_key(key, value)
627
685
  end
628
- end
629
- # Same as UdatCollection#set_value_for_key.
630
- def []=(key, value)
631
- set_value_for_key(key, value)
632
- end
633
686
 
634
- # Returns the value at a numeric index, or nil, if the index is out of
635
- # bounds.
636
- def value_by_index(index)
637
- index = index.to_int
638
- synchronize do
639
- entry = @entries[index]
640
- return entry ? entry[1] : nil
687
+ # Returns the value at a numeric index, or nil, if the index is out of
688
+ # bounds.
689
+ def value_by_index(index)
690
+ index = index.to_int
691
+ synchronize do
692
+ entry = @entries[index]
693
+ return entry ? entry[1] : nil
694
+ end
641
695
  end
642
- end
643
- # Returns the key at a numeric index, or nil, if the index is out of
644
- # bounds or at the given index there is only a value without key.
645
- def key_by_index(index)
646
- index = index.to_int
647
- synchronize do
648
- entry = @entries[index]
649
- return entry ? entry[0] : nil
696
+ # Returns the key at a numeric index, or nil, if the index is out of
697
+ # bounds or at the given index there is only a value without key.
698
+ def key_by_index(index)
699
+ index = index.to_int
700
+ synchronize do
701
+ entry = @entries[index]
702
+ return entry ? entry[0] : nil
703
+ end
650
704
  end
651
- end
652
- # Returns an Array of values having a given key.
653
- def values_by_key(key)
654
- key = key.to_udat
655
- synchronize do
656
- require_index_hashes
657
- if key.kind_of? UdatCollection and key.unordered?
658
- indicies = @all_unordered_key_indicies[
659
- UdatUnorderedWrapper.new(key)
660
- ] || []
661
- else
662
- indicies = (@key_indicies[key] || []) + (
663
- @unordered_key_indicies[UdatUnorderedWrapper.new(key)] || []
705
+ # Returns an Array of values having no key.
706
+ def values_without_key
707
+ synchronize do
708
+ require_index_hashes
709
+ return (
710
+ (@key_indicies[nil] || []).collect { |index| @entries[index][1] }
664
711
  )
665
- indicies.uniq!
666
- indicies.sort!
667
712
  end
668
- return indicies.collect { |index| @entries[index][1] }
669
713
  end
670
- end
671
- # Returns an Array of keys having a given value.
672
- def keys_by_value(value)
673
- value = value.to_udat
674
- synchronize do
675
- require_index_hashes
676
- if value.kind_of? UdatCollection and value.unordered?
677
- indicies = @all_unordered_value_indicies[
678
- UdatUnorderedWrapper.new(value)
679
- ] || []
680
- else
681
- indicies = (value_indicies[key] || []) + (
682
- @unordered_value_indicies[UdatUnorderedWrapper.new(value)] || []
683
- )
684
- indicies.uniq!
685
- indicies.sort!
714
+ # Returns an Array of values having a given key.
715
+ def values_by_key(key)
716
+ key = key.to_udat
717
+ synchronize do
718
+ require_index_hashes
719
+ if key.kind_of? Collection and key.unordered?
720
+ indicies = @all_unordered_key_indicies[
721
+ UnorderedWrapper.new(key)
722
+ ] || []
723
+ else
724
+ indicies = (@key_indicies[key] || []) + (
725
+ @unordered_key_indicies[UnorderedWrapper.new(key)] || []
726
+ )
727
+ indicies.uniq!
728
+ indicies.sort!
729
+ end
730
+ return indicies.collect { |index| @entries[index][1] }
731
+ end
732
+ end
733
+ # Returns an Array of keys having a given value. The Array may contain
734
+ # nil's for values having no key.
735
+ def keys_by_value(value)
736
+ value = value.to_udat
737
+ synchronize do
738
+ require_index_hashes
739
+ if value.kind_of? Collection and value.unordered?
740
+ indicies = @all_unordered_value_indicies[
741
+ UnorderedWrapper.new(value)
742
+ ] || []
743
+ else
744
+ indicies = (@value_indicies[value] || []) + (
745
+ @unordered_value_indicies[UnorderedWrapper.new(value)] || []
746
+ )
747
+ indicies.uniq!
748
+ indicies.sort!
749
+ end
750
+ return indicies.collect { |index| @entries[index][0] }
751
+ end
752
+ end
753
+ # Returns the last value without key.
754
+ def value_without_key
755
+ values_without_key.last
756
+ end
757
+ # Returns the last value having a given key.
758
+ def value_by_key(key)
759
+ values_by_key(key).last
760
+ end
761
+ # Returns the last key having a given value.
762
+ def key_by_value(value)
763
+ keys_by_value(value).last
764
+ end
765
+ # Returns true, if the value is contained in the collection.
766
+ def include?(value)
767
+ value = value.to_udat
768
+ synchronize do
769
+ require_index_hashes
770
+ if value.kind_of? Collection and value.unordered?
771
+ return (@all_unordered_value_indicies[
772
+ UnorderedWrapper.new(value)
773
+ ] || []).empty? ? false : true
774
+ else
775
+ return ((@value_indicies[value] || []) + (
776
+ @unordered_value_indicies[UnorderedWrapper.new(value)] || []
777
+ )).empty? ? false : true
778
+ end
686
779
  end
687
- return indicies.collect { |index| @entries[index][0] }
688
780
  end
689
- end
690
- # Returns the last value having a given key.
691
- def value_by_key(key)
692
- values_by_key(key).last
693
- end
694
- # Returns the last key having a given value.
695
- def key_by_value(value)
696
- keys_by_value(value).last
697
- end
698
781
 
699
- # Behaves differently depending on the type of the argument.
700
- # If the argument is an Integer the method behaves same as
701
- # UdatCollection#value_by_index.
702
- # Otherwise the method behaves same as UdatCollection#value_by_key.
703
- def [](key)
704
- if key.kind_of? Integer
705
- return value_by_index(key)
706
- else
707
- return value_by_key(key)
782
+ # Behaves differently depending on the type of the argument.
783
+ # If the argument is an Integer, the method behaves same as
784
+ # Udat::Collection#value_by_index. If the argument is nil, the method
785
+ # behaves like Udat::Collection#value_without_key. Otherwise the method
786
+ # behaves same as Udat::Collection#value_by_key.
787
+ def [](key)
788
+ if key.nil?
789
+ return value_without_key
790
+ elsif key.kind_of? Integer
791
+ return value_by_index(key)
792
+ else
793
+ return value_by_key(key)
794
+ end
708
795
  end
709
- end
710
- # Same as UdatCollection#[], but raises an error, if no value was found.
711
- # If an additional second argument is passed, an error will also be
712
- # raised if the tag (i.e. type) of the value is not equal to the second
713
- # argument.
714
- def fetch(*args)
715
- if args.length == 1
716
- value = self[args[0]]
717
- unless value
718
- raise IndexError, "Value for the given key or index not found."
796
+ # Same as Udat::Collection#[], but raises an error, if no value was
797
+ # found. If an additional second argument is passed, an error will also
798
+ # be raised if the tag (i.e. type) of the value is not equal to the
799
+ # second argument.
800
+ def fetch(*args)
801
+ if args.length == 1
802
+ value = self[args[0]]
803
+ unless value
804
+ raise IndexError, "Value for the given key or index not found."
805
+ end
806
+ return value
807
+ elsif args.length == 2
808
+ value = fetch(args[0])
809
+ unless value.tag == args[1]
810
+ raise UdatTagMismatch, "UDAT tag mismatch."
811
+ end
812
+ return value
813
+ else
814
+ raise ArgumentError, "Wrong number of arguments supplied."
815
+ end
816
+ end
817
+ # Same as Udat::Collection#fetch, but raises an error, if the value is
818
+ # not an Udat::Collection.
819
+ def fetch_collection(*args)
820
+ value = fetch(*args)
821
+ if value.scalar?
822
+ raise UdatTypeMismatch,
823
+ "Scalar value found, where a collection was expected."
719
824
  end
720
825
  return value
721
- elsif args.length == 2
722
- value = fetch(args[0])
723
- unless value.tag == args[1]
724
- raise UdatTagMismatch, "UDAT tag mismatch."
826
+ end
827
+ # Same as Udat::Collection#fetch, but raises an error, if the value is
828
+ # not an Udat::Scalar.
829
+ def fetch_scalar(*args)
830
+ value = fetch(*args)
831
+ if value.collection?
832
+ raise UdatTypeMismatch,
833
+ "Collection found, where a scalar value was expected."
725
834
  end
726
835
  return value
727
- else
728
- raise ArgumentError, "Wrong number of arguments supplied."
729
836
  end
730
- end
731
- # Same as UdatCollection#fetch, but raises an error, if the value is
732
- # not an UdatCollection.
733
- def fetch_collection(*args)
734
- value = fetch(*args)
735
- if value.scalar?
736
- raise UdatTypeMismatch,
737
- "Scalar value found, where a collection was expected."
738
- end
739
- return value
740
- end
741
- # Same as UdatCollection#fetch, but raises an error, if the value is
742
- # not an UdatScalar.
743
- def fetch_scalar(*args)
744
- value = fetch(*args)
745
- if value.collection?
746
- raise UdatTypeMismatch,
747
- "Collection found, where a scalar value was expected."
748
- end
749
- return value
750
- end
751
- # Returns the first value of the collection, or nil if empty.
752
- def first
753
- self[0]
754
- end
755
- # Returns the last value of the collection, or nil if empty.
756
- def last
757
- self[-1]
758
- end
759
-
760
- # Returns the number of values in the collection.
761
- def length
762
- synchronize do
763
- @entries.length
837
+ # Returns the first value of the collection, or nil if empty.
838
+ def first
839
+ self[0]
764
840
  end
765
- end
766
- alias size length
767
- # Returns true, if the collection is empty.
768
- def empty?
769
- length == 0
770
- end
771
- # Calls a given block for each numeric index.
772
- def each_index
773
- synchronize do
774
- (0...length).each { |i| yield i }
841
+ # Returns the last value of the collection, or nil if empty.
842
+ def last
843
+ self[-1]
775
844
  end
776
- return self
777
- end
778
845
 
779
- # Returns an Array containing the key/value pairs each as an Array of
780
- # size 2. If there is no key, the first element of the sub Array is nil.
781
- def key_value_pairs
782
- synchronize do
783
- return @entries.collect { |entry| entry.dup }
846
+ # Returns the number of values in the collection.
847
+ def length
848
+ synchronize do
849
+ @entries.length
850
+ end
784
851
  end
785
- end
786
- # Returns an Array containing all keys of the collection.
787
- def keys
788
- keys = nil
789
- synchronize do
790
- keys = @entries.collect { |key, value| key }
791
- end
792
- keys.compact!
793
- return keys
794
- end
795
- # Returns an Array containing all values of the collection.
796
- def values
797
- synchronize do
798
- return @entries.collect { |key, value| value }
852
+ alias size length
853
+ # Returns true, if the collection is empty.
854
+ def empty?
855
+ length == 0
856
+ end
857
+ # Calls a given block for each numeric index.
858
+ def each_index
859
+ synchronize do
860
+ (0...length).each { |i| yield i }
861
+ end
862
+ return self
799
863
  end
800
- end
801
864
 
802
- # Returns a hash, where each key is mapped to the respective value.
803
- def to_hash
804
- hash = {}
805
- synchronize do
806
- @entries.each do |key, value|
807
- next if key.nil?
808
- hash[key] = value unless hash.has_key? key
865
+ # Returns an Array containing the key/value pairs each as an Array of
866
+ # size 2. If there is no key, the first element of the sub Array is
867
+ # nil.
868
+ def key_value_pairs
869
+ synchronize do
870
+ return @entries.collect { |entry| entry.dup }
809
871
  end
810
872
  end
811
- return hash
812
- end
813
- # Same as UdatCollection#values.
814
- def to_ary
815
- values
816
- end
817
- alias to_a values
818
-
819
- # Appends the values (and corresponding keys) of another UdatCollection.
820
- def concat(other)
821
- synchronize do
822
- other.key_value_pairs.each do |key, value|
823
- if key.nil?
824
- append_value(value)
825
- else
826
- append_pair(key, value)
873
+ # Returns an Array containing all keys of the collection.
874
+ def keys
875
+ keys = nil
876
+ synchronize do
877
+ keys = @entries.collect { |key, value| key }
878
+ end
879
+ keys.compact!
880
+ return keys
881
+ end
882
+ # Returns an Array containing all values of the collection.
883
+ def values
884
+ synchronize do
885
+ return @entries.collect { |key, value| value }
886
+ end
887
+ end
888
+
889
+ # Returns a hash, where each key is mapped to the respective value.
890
+ def to_hash
891
+ hash = {}
892
+ synchronize do
893
+ @entries.each do |key, value|
894
+ next if key.nil?
895
+ hash[key] = value unless hash.has_key? key
827
896
  end
828
897
  end
898
+ return hash
829
899
  end
830
- return self
831
- end
832
- # Replaces the values (and corresponding keys) with the values/keys of
833
- # another UdatCollection.
834
- def replace(other)
835
- synchronize do
836
- clear
837
- concat(other)
900
+ # Same as Udat::Collection#values.
901
+ def to_ary
902
+ values
838
903
  end
839
- return self
840
- end
904
+ alias to_a values
841
905
 
842
- # Returns the encoded form of the content as a string.
843
- # This method is used by Udat#encode, which returns the complete encoding
844
- # of the data (including the tag).
845
- def encoded_content
846
- synchronize do
847
- return (
848
- @entries.empty? ? "~" :
849
- @entries.collect do |key, value|
850
- if key
851
- "<#{key.encode}>[#{value.encode}]"
906
+ # Appends the values (and corresponding keys) of another
907
+ # Udat::Collection and returns self.
908
+ def concat(other)
909
+ synchronize do
910
+ other.key_value_pairs.each do |key, value|
911
+ if key.nil?
912
+ append_value(value)
852
913
  else
853
- "[#{value.encode}]"
914
+ append_pair(key, value)
854
915
  end
855
- end.join
856
- )
916
+ end
917
+ end
918
+ return self
919
+ end
920
+ # Replaces the values (and corresponding keys) with the values/keys of
921
+ # another Udat::Collection and returns self.
922
+ def replace(other)
923
+ synchronize do
924
+ clear
925
+ concat(other)
926
+ end
927
+ return self
857
928
  end
858
- end
859
929
 
860
- # Same as UdatCollection#ordered?.
861
- def ordered
862
- synchronize do
863
- return @ordered
930
+ # Returns the encoded form of the content as a string.
931
+ # This method is used by Udat::Node#encode_part and will be declared
932
+ # protected in future.
933
+ def encoded_content
934
+ synchronize do
935
+ return (
936
+ @entries.empty? ? "~" :
937
+ @entries.collect do |key, value|
938
+ if key
939
+ "<#{key.encode_part}>[#{value.encode_part}]"
940
+ else
941
+ "[#{value.encode_part}]"
942
+ end
943
+ end.join
944
+ )
945
+ end
864
946
  end
865
- end
866
- # Returns false, if the order of the contents of this collection is to be
867
- # ignored for comparisons.
868
- def ordered?
869
- self.ordered
870
- end
871
- # Returns true, if the order of the contents of this collection is to be
872
- # ignored for comparisons.
873
- def unordered?
874
- not self.ordered
875
- end
876
- # If set to false, the order of the contents of this collection will be
877
- # ignored for comparisons.
878
- def ordered=(ordered)
879
- synchronize do
880
- if ordered == false
881
- @ordered = false
882
- else
883
- @ordered = true
947
+
948
+ # Same as Udat::Collection#ordered?.
949
+ def ordered
950
+ synchronize do
951
+ return @ordered
884
952
  end
885
953
  end
886
- end
887
- # Same as UdatCollection#ordered = false, but returns self.
888
- def unordered!
889
- self.ordered = false
890
- return self
891
- end
892
- # Same as UdatCollection#ordered = true, but returns self.
893
- def ordered!
894
- self.ordered = true
895
- return self
896
- end
954
+ # Returns false, if the order of the contents of this collection is to
955
+ # be ignored for comparisons.
956
+ def ordered?
957
+ self.ordered
958
+ end
959
+ # Returns true, if the order of the contents of this collection is to
960
+ # be ignored for comparisons.
961
+ def unordered?
962
+ not self.ordered
963
+ end
964
+ # If set to false, the order of the contents of this collection will be
965
+ # ignored for comparisons.
966
+ def ordered=(ordered)
967
+ synchronize do
968
+ if ordered == false
969
+ @ordered = false
970
+ else
971
+ @ordered = true
972
+ end
973
+ end
974
+ end
975
+ # Same as Udat::Collection#ordered = false, but returns self.
976
+ def unordered!
977
+ self.ordered = false
978
+ return self
979
+ end
980
+ # Same as Udat::Collection#ordered = true, but returns self.
981
+ def ordered!
982
+ self.ordered = true
983
+ return self
984
+ end
897
985
 
898
- # Same as Udat#inspect, but in case of unordered collections it prepends
899
- # "udat-unordered" instead of just "udat".
900
- def inspect
901
- if ordered?
902
- return super
903
- else
904
- "udat-unordered{#{self.encode}}"
986
+ # Same as Udat::Node#inspect, but in case of unordered collections it
987
+ # prepends "udat-unordered" instead of just "udat".
988
+ def inspect
989
+ if ordered?
990
+ return super
991
+ else
992
+ "udat-unordered#{self.encode_document}"
993
+ end
905
994
  end
906
- end
907
995
 
908
- def ==(other)
909
- return false unless self.class == other.class
910
- if self.unordered? or other.unordered?
911
- return UdatUnorderedWrapper.new(self) ==
912
- UdatUnorderedWrapper.new(other)
913
- else
914
- return super
996
+ # Returns true, if class, tag and content are matching another object.
997
+ # The order of the content is only significant for comparison, if both
998
+ # objects have the Udat::Collection#ordered attribute set to true.
999
+ def ==(other)
1000
+ return false unless self.class == other.class
1001
+ if self.unordered? or other.unordered?
1002
+ return UnorderedWrapper.new(self) ==
1003
+ UnorderedWrapper.new(other)
1004
+ else
1005
+ return super
1006
+ end
915
1007
  end
1008
+
916
1009
  end
917
1010
 
918
- end
919
1011
 
1012
+ # Class of Udat::Node's holding scalar values (stored as a String).
1013
+ class Scalar < Node
920
1014
 
921
- # Class of UDAT objects holding scalar values (stored as a String).
922
- class UdatScalar < Udat
1015
+ public_class_method :new
1016
+ # Creates a new Udat::Scalar with a given tag (which may be nil) and a
1017
+ # given content, which is transformed to a string (via Object#to_s).
1018
+ # It is not recommended to use this method. Use Object#to_udat instead.
1019
+ def initialize(tag, content)
1020
+ super tag
1021
+ self.content = content
1022
+ end
923
1023
 
924
- public_class_method :new
925
- # Creates a new Udat object with a given tag (which may be nil) and a
926
- # given content, which is transformed to a string (via 'to_s').
927
- # It is not recommended to use this method. Use Object#to_udat instead.
928
- def initialize(tag, content)
929
- super tag
930
- self.content = content
931
- end
1024
+ # Content of the scalar (a String).
1025
+ attr_reader :content
1026
+ # Sets the content. The content is casted to a string.
1027
+ def content=(content)
1028
+ @content = content.to_s
1029
+ end
932
1030
 
933
- # Content of the scalar (a String).
934
- attr_reader :content
935
- # Sets the content. The content is casted to a string.
936
- def content=(content)
937
- @content = content.to_s
938
- end
1031
+ # Same as Udat::Scalar#content.
1032
+ def to_s
1033
+ content
1034
+ end
1035
+ # Returns the content casted to an Integer.
1036
+ def to_i
1037
+ content.to_i
1038
+ end
939
1039
 
940
- # Same as UdatScalar#content.
941
- def to_s
942
- content
943
- end
944
- # Returns the content casted to an Integer.
945
- def to_i
946
- content.to_i
947
- end
1040
+ # Returns true, if the (String representation of the) content is empty.
1041
+ def empty?
1042
+ self.to_s.empty?
1043
+ end
948
1044
 
949
- # Returns true, if the (String representation of the) content is empty.
950
- def empty?
951
- self.to_s.empty?
952
- end
1045
+ # Returns the encoded form of the content as a string. In this case
1046
+ # this is simply an escaped version of the String. This method will be
1047
+ # declared protected in future versions.
1048
+ def encoded_content
1049
+ Udat::escape_string(self.to_s)
1050
+ end
953
1051
 
954
- # Returns the encoded form of the content as a string.
955
- # In this case this is simply an escaped version of the String.
956
- # This method is used by Udat#encode, which returns the complete encoding
957
- # of the data (including the tag).
958
- def encoded_content
959
- escape_string(self.to_s)
960
1052
  end
961
1053
 
962
1054
  end
963
1055
 
964
1056
 
965
1057
  class Object
966
- # Casts the Object to a nUdatScalar object, optionally with a given tag.
967
- # Unless overwritten by sub classes, only the string representation (as
968
- # returned by Object#to_s) will be stored as content in the UdatScalar
969
- # object.
1058
+ # Casts the Object to an Udat::Scalar object, optionally with a given
1059
+ # tag. Unless overwritten by sub classes, only the string representation
1060
+ # (as returned by Object#to_s) will be stored as content in the
1061
+ # Udat::Scalar object.
970
1062
  def to_udat(tag = nil)
971
- UdatScalar.new(tag, self)
1063
+ Udat::Scalar.new(tag, self)
972
1064
  end
973
1065
  end
974
1066
 
975
1067
  class Array
976
- # Casts the Array to an UdatCollection object, optionally with a given
977
- # tag. The UdatCollection object is marked to be ordered. Each element of
978
- # the array may be an Array of size 2, in which case the 2 entries are
1068
+ # Casts the Array to an Udat::Collection object, optionally with a given
1069
+ # tag. The Udat::Collection object is marked to be ordered. Each element
1070
+ # of the array may be an Array of size 2, in which case the 2 entries are
979
1071
  # used as key and value. If an element of the Array is not an Array, it
980
1072
  # will be used as value without an associated key.
981
1073
  def to_udat(tag = nil)
982
- UdatCollection.new(tag, self)
1074
+ Udat::Collection.new(tag, self)
983
1075
  end
984
1076
  end
985
1077
 
986
1078
  class Hash
987
- # Casts the Hash to an UdatCollection object, optionally with a given
988
- # tag. The UdatCollection object is marked to be unordered.
1079
+ # Casts the Hash to an Udat::Collection object, optionally with a given
1080
+ # tag. The Udat::Collection object is marked to be unordered.
989
1081
  def to_udat(tag = nil)
990
- UdatCollection.new(tag, self.to_a).unordered!
1082
+ Udat::Collection.new(tag, self.to_a).unordered!
991
1083
  end
992
1084
  end
993
1085
 
994
1086
  class String
995
- # Calls Udat.parse(self).
1087
+ # Deprecated. Calls Udat::Node.parse_part(self).
1088
+ # Use String#parse_udat_document instead.
996
1089
  def parse_udat
997
- Udat.parse(self)
1090
+ Udat::Node.parse_part(self)
1091
+ end
1092
+ # Calls Udat::Node.parse_document(self).
1093
+ def parse_udat_document
1094
+ Udat::Node.parse_document(self)
998
1095
  end
999
1096
  end
1000
1097
 
1001
1098
  class IO
1002
- # Calls Udat.read_from_stream(self).
1099
+ # Calls Udat::Node.read_from_stream(self).
1003
1100
  def read_udat
1004
- Udat.read_from_stream(self)
1101
+ Udat::Node.read_from_stream(self)
1005
1102
  end
1006
- # Calls Udat#write_to_stream(self), after converting the object to an
1007
- # Udat object by calling Object#to_udat.
1103
+ # Calls Udat::Node#write_to_stream(self), after converting the object to
1104
+ # an Udat::Node by calling Object#to_udat. Returns self.
1008
1105
  def write_udat(object)
1009
1106
  object.to_udat.write_to_stream(self)
1107
+ return self
1010
1108
  end
1011
1109
  end
1012
1110
 
1111
+ # Deprecated constant for backwards compatibility.
1112
+ UdatCollection = Udat::Collection
1113
+
1114
+ # Deprecated constant for backwards compatibility.
1115
+ UdatScalar = Udat::Scalar
1116
+
1117
+
1118
+