udat 1.2.1 → 1.3.0

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