ginjo-rfm 3.0.9 → 3.0.10
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +13 -5
- data/CHANGELOG.md +61 -40
- data/README.md +8 -8
- data/lib/rfm.rb +66 -67
- data/lib/rfm/VERSION +1 -1
- data/lib/rfm/base.rb +237 -241
- data/lib/rfm/database.rb +38 -24
- data/lib/rfm/error.rb +25 -25
- data/lib/rfm/layout.rb +217 -195
- data/lib/rfm/metadata/datum.rb +37 -37
- data/lib/rfm/metadata/field.rb +42 -39
- data/lib/rfm/metadata/field_control.rb +72 -72
- data/lib/rfm/metadata/layout_meta.rb +32 -32
- data/lib/rfm/metadata/resultset_meta.rb +74 -74
- data/lib/rfm/metadata/script.rb +3 -3
- data/lib/rfm/metadata/value_list_item.rb +30 -30
- data/lib/rfm/record.rb +80 -77
- data/lib/rfm/resultset.rb +63 -65
- data/lib/rfm/server.rb +31 -23
- data/lib/rfm/utilities/case_insensitive_hash.rb +4 -1
- data/lib/rfm/utilities/compound_query.rb +100 -101
- data/lib/rfm/utilities/config.rb +228 -218
- data/lib/rfm/utilities/connection.rb +65 -57
- data/lib/rfm/utilities/core_ext.rb +114 -119
- data/lib/rfm/utilities/factory.rb +122 -126
- data/lib/rfm/utilities/sax_parser.rb +1058 -1046
- data/lib/rfm/utilities/scope.rb +64 -0
- data/lib/rfm/version.rb +23 -7
- metadata +40 -39
@@ -9,193 +9,189 @@
|
|
9
9
|
module Rfm
|
10
10
|
|
11
11
|
module Factory
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
12
|
+
# Acquired from Rfm::Base
|
13
|
+
@models ||= []
|
14
|
+
|
15
|
+
extend Config
|
16
|
+
config :parent=>'Rfm::Config'
|
17
|
+
|
18
|
+
class ServerFactory < Rfm::CaseInsensitiveHash
|
20
19
|
def [](*args)
|
21
|
-
|
22
|
-
|
20
|
+
options = Factory.get_config(*args)
|
21
|
+
host = options[:strings].delete_at(0) || options[:host]
|
23
22
|
super(host) || (self[host] = Rfm::Server.new(*args)) #(host, options.rfm_filter(:account_name, :password, :delete=>true)))
|
24
23
|
# This part reconfigures the named server, if you pass it new config in the [] method.
|
25
24
|
# This breaks some specs in all [] methods in Factory. Consider undoing this. See readme-dev.
|
26
|
-
|
27
|
-
|
25
|
+
# super(host).config(options) if (options)
|
26
|
+
# super(host)
|
28
27
|
end
|
29
|
-
|
30
28
|
end # ServerFactory
|
31
|
-
|
32
|
-
|
29
|
+
|
30
|
+
|
33
31
|
class DbFactory < Rfm::CaseInsensitiveHash # :nodoc: all
|
34
|
-
|
35
|
-
#
|
36
|
-
# config :parent=>'@server'
|
32
|
+
# extend Config
|
33
|
+
# config :parent=>'@server'
|
37
34
|
|
38
|
-
|
39
35
|
def initialize(server)
|
40
|
-
|
41
|
-
|
36
|
+
extend Config
|
37
|
+
config :parent=>'@server'
|
42
38
|
@server = server
|
43
39
|
@loaded = false
|
44
40
|
end
|
45
|
-
|
41
|
+
|
46
42
|
def [](*args)
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
43
|
+
# was: (dbname, acnt=nil, pass=nil)
|
44
|
+
options = get_config(*args)
|
45
|
+
name = options[:strings].delete_at(0) || options[:database]
|
46
|
+
#account_name = options[:strings].delete_at(0) || options[:account_name]
|
47
|
+
#password = options[:strings].delete_at(0) || options[:password]
|
52
48
|
super(name) || (self[name] = Rfm::Database.new(@server, *args)) #(name, account_name, password, @server))
|
53
49
|
# This part reconfigures the named database, if you pass it new config in the [] method.
|
54
|
-
|
55
|
-
|
50
|
+
# super(name).config({:account_name=>account_name, :password=>password}.merge(options)) if (account_name or password or options)
|
51
|
+
# super(name)
|
56
52
|
end
|
57
|
-
|
53
|
+
|
58
54
|
def all
|
59
55
|
if !@loaded
|
60
|
-
|
61
|
-
|
62
|
-
|
56
|
+
c = Connection.new('-dbnames', {}, {:grammar=>'FMPXMLRESULT'}, @server)
|
57
|
+
c.parse('fmpxml_minimal.yml', {})['data'].each{|k,v| (self[k] = Rfm::Database.new(v['text'], @server)) if k.to_s != '' && v['text']}
|
58
|
+
#r = c.parse('fmpxml_minimal.yml', {})
|
63
59
|
@loaded = true
|
64
60
|
end
|
65
61
|
self
|
66
62
|
end
|
67
|
-
|
63
|
+
|
68
64
|
def names
|
69
|
-
|
65
|
+
self.values.collect{|v| v.name}
|
70
66
|
end
|
71
|
-
|
67
|
+
|
72
68
|
end # DbFactory
|
73
|
-
|
74
|
-
|
75
|
-
|
69
|
+
|
70
|
+
|
71
|
+
|
76
72
|
class LayoutFactory < Rfm::CaseInsensitiveHash # :nodoc: all
|
77
73
|
|
78
|
-
#
|
79
|
-
#
|
80
|
-
|
74
|
+
# extend Config
|
75
|
+
# config :parent=>'@database'
|
76
|
+
|
81
77
|
def initialize(server, database)
|
82
|
-
|
83
|
-
|
78
|
+
extend Config
|
79
|
+
config :parent=>'@database'
|
84
80
|
@server = server
|
85
81
|
@database = database
|
86
82
|
@loaded = false
|
87
83
|
end
|
88
|
-
|
84
|
+
|
89
85
|
def [](*args) # was layout_name
|
90
|
-
|
91
|
-
|
86
|
+
options = get_config(*args)
|
87
|
+
name = options[:strings].delete_at(0) || options[:layout]
|
92
88
|
super(name) || (self[name] = Rfm::Layout.new(@database, *args)) #(name, @database, options))
|
93
89
|
# This part reconfigures the named layout, if you pass it new config in the [] method.
|
94
|
-
|
95
|
-
|
90
|
+
# super(name).config({:layout=>name}.merge(options)) if options
|
91
|
+
# super(name)
|
96
92
|
end
|
97
|
-
|
93
|
+
|
98
94
|
def all
|
99
95
|
if !@loaded
|
100
|
-
|
101
|
-
|
96
|
+
c = Connection.new('-layoutnames', {"-db" => @database.name}, {:grammar=>'FMPXMLRESULT'}, @database)
|
97
|
+
c.parse('fmpxml_minimal.yml', {})['data'].each{|k,v| (self[k] = Rfm::Layout.new(v['text'], @database)) if k.to_s != '' && v['text']}
|
102
98
|
@loaded = true
|
103
99
|
end
|
104
100
|
self
|
105
101
|
end
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
102
|
+
|
103
|
+
def names
|
104
|
+
values.collect{|v| v.name}
|
105
|
+
end
|
106
|
+
|
107
|
+
# Acquired from Rfm::Base
|
108
|
+
def modelize(filter = /.*/)
|
109
|
+
all.values.each{|lay| lay.modelize if lay.name.match(filter)}
|
110
|
+
models
|
111
|
+
end
|
112
|
+
|
113
|
+
# Acquired from Rfm::Base
|
114
|
+
def models
|
115
|
+
rslt = {}
|
116
|
+
each do |k,lay|
|
117
|
+
layout_models = lay.models
|
118
|
+
rslt[k] = layout_models if (!layout_models.nil? && !layout_models.empty?)
|
119
|
+
end
|
120
|
+
rslt
|
121
|
+
end
|
122
|
+
|
127
123
|
end # LayoutFactory
|
128
|
-
|
129
|
-
|
130
|
-
|
124
|
+
|
125
|
+
|
126
|
+
|
131
127
|
class ScriptFactory < Rfm::CaseInsensitiveHash # :nodoc: all
|
132
128
|
|
133
|
-
#
|
134
|
-
#
|
135
|
-
|
129
|
+
# extend Config
|
130
|
+
# config :parent=>'@database'
|
131
|
+
|
136
132
|
def initialize(server, database)
|
137
|
-
|
138
|
-
|
133
|
+
extend Config
|
134
|
+
config :parent=>'@database'
|
139
135
|
@server = server
|
140
136
|
@database = database
|
141
137
|
@loaded = false
|
142
138
|
end
|
143
|
-
|
139
|
+
|
144
140
|
def [](script_name)
|
145
141
|
super or (self[script_name] = Rfm::Metadata::Script.new(script_name, @database))
|
146
142
|
end
|
147
|
-
|
143
|
+
|
148
144
|
def all
|
149
145
|
if !@loaded
|
150
|
-
|
151
|
-
|
146
|
+
c = Connection.new('-scriptnames', {"-db" => @database.name}, {:grammar=>'FMPXMLRESULT'}, @database)
|
147
|
+
c.parse('fmpxml_minimal.yml', {})['data'].each{|k,v| (self[k] = Rfm::Metadata::Script.new(v['text'], @database)) if k.to_s != '' && v['text']}
|
152
148
|
@loaded = true
|
153
149
|
end
|
154
150
|
self
|
155
151
|
end
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
152
|
+
|
153
|
+
def names
|
154
|
+
values.collect{|v| v.name}
|
155
|
+
end
|
156
|
+
|
161
157
|
end # ScriptFactory
|
162
|
-
|
163
|
-
|
164
|
-
|
158
|
+
|
159
|
+
|
160
|
+
|
165
161
|
class << self
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
162
|
+
|
163
|
+
# Acquired from Rfm::Base
|
164
|
+
attr_accessor :models
|
165
|
+
# Shortcut to Factory.db().layouts.modelize()
|
166
|
+
# If first parameter is regex, it is used for modelize filter.
|
167
|
+
# Otherwise, parameters are passed to Factory.database
|
168
|
+
def modelize(*args)
|
169
|
+
regx = args[0].is_a?(Regexp) ? args.shift : /.*/
|
170
|
+
db(*args).layouts.modelize(regx)
|
171
|
+
end
|
172
|
+
|
173
|
+
def servers
|
174
|
+
@servers ||= ServerFactory.new
|
175
|
+
end
|
176
|
+
|
177
|
+
# Returns Rfm::Server instance, given config hash or array
|
178
|
+
def server(*conf)
|
179
|
+
Server.new(*conf)
|
180
|
+
end
|
181
|
+
|
182
|
+
# Returns Rfm::Db instance, given config hash or array
|
183
|
+
def db(*conf)
|
184
|
+
Database.new(*conf)
|
185
|
+
end
|
186
|
+
|
187
|
+
alias_method :database, :db
|
188
|
+
|
189
|
+
# Returns Rfm::Layout instance, given config hash or array
|
190
|
+
def layout(*conf)
|
191
|
+
Layout.new(*conf)
|
192
|
+
end
|
197
193
|
|
198
194
|
end # class << self
|
199
|
-
|
195
|
+
|
200
196
|
end # Factory
|
201
|
-
end # Rfm
|
197
|
+
end # Rfm
|
@@ -26,15 +26,15 @@
|
|
26
26
|
# irb -rubygems -I./ -r lib/rfm/utilities/sax_parser.rb
|
27
27
|
# SaxParser.parse(io, template=nil, initial_object=nil, parser=nil, options={})
|
28
28
|
# io: xml-string or xml-file-path or file-io or string-io
|
29
|
-
#
|
30
|
-
#
|
29
|
+
# template: file-name, yaml, xml, symbol, or hash
|
30
|
+
# initial_object: the parent object - any object to which the resulting build will be attached to.
|
31
31
|
# parser: backend parser symbol or custom backend handler instance
|
32
32
|
# options: extra options
|
33
33
|
#
|
34
34
|
#
|
35
35
|
# Note: 'attach: cursor' puts the object in the cursor & stack but does not attach it to the parent.
|
36
36
|
# 'attach: none' prevents the object from entering the cursor or stack.
|
37
|
-
#
|
37
|
+
# Both of these will still allow processing of attributes and subelements.
|
38
38
|
#
|
39
39
|
# Note: Attribute attachment is controlled first by the attributes' model's :attributes hash (controls individual attrs),
|
40
40
|
# and second by the base model's main hash. Any model's main hash :attach_attributes only controls
|
@@ -54,23 +54,23 @@
|
|
54
54
|
#
|
55
55
|
# YAML structure defining a SAX xml parsing template.
|
56
56
|
# Options:
|
57
|
-
# initialize_with:
|
58
|
-
# elements:
|
59
|
-
# attributes:
|
60
|
-
# class:
|
61
|
-
#
|
62
|
-
#
|
63
|
-
#
|
64
|
-
#
|
65
|
-
# before_close:
|
66
|
-
# as_name:
|
67
|
-
# delimiter:
|
68
|
-
#
|
69
|
-
#
|
70
|
-
#
|
71
|
-
#
|
72
|
-
#
|
73
|
-
#
|
57
|
+
# initialize_with: OBSOLETE? string, symbol, or array (object, method, params...). Should return new object. See Rfm::SaxParser::Cursor#get_callback.
|
58
|
+
# elements: array of element hashes [{'name'=>'element-tag'},...]
|
59
|
+
# attributes: array of attribute hashes {'name'=>'attribute-name'} UC
|
60
|
+
# class: string-or-class: class name for new element
|
61
|
+
# attach: string: shared, _shared_var_name, private, hash, array, cursor, none - how to attach this element or attribute to #object.
|
62
|
+
# array: [0]string of above, [1..-1]new_element_callback options (see get_callback method).
|
63
|
+
# attach_elements: string: same as 'attach' - how to attach ANY subelements to this model's object, unless they have their own 'attach' specification.
|
64
|
+
# attach_attributes: string: same as 'attach' - how to attach ANY attributes to this model's object, unless they have their own 'attach' specification.
|
65
|
+
# before_close: string, symbol, or array (object, method, params...). See Rfm::SaxParser::Cursor#get_callback.
|
66
|
+
# as_name: string: store element or attribute keyed as specified
|
67
|
+
# delimiter: string: attribute/hash key to delineate objects with identical tags
|
68
|
+
# create_accessors: string or array: all, private, shared, hash, none
|
69
|
+
# accessor: string: all, private, shared, hash, none
|
70
|
+
# element_handler: NOT-USED? string, symbol, or array (object, method, params...). Should return new object. See Rfm::SaxParser::Cursor#get_callback.
|
71
|
+
# Default attach prefs are 'cursor'.
|
72
|
+
# Use this when all new-element operations should be offloaded to custom class or module.
|
73
|
+
# Should return an instance of new object.
|
74
74
|
# translate: UC Consider adding a 'translate' option to point to a method on the current model's object to use to translate values for attributes.
|
75
75
|
#
|
76
76
|
#
|
@@ -82,815 +82,827 @@ require 'forwardable'
|
|
82
82
|
require 'stringio'
|
83
83
|
|
84
84
|
module Rfm
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
@object
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
85
|
+
module SaxParser
|
86
|
+
|
87
|
+
RUBY_VERSION_NUM = RUBY_VERSION[0,3].to_f
|
88
|
+
|
89
|
+
PARSERS = {}
|
90
|
+
|
91
|
+
# These defaults can be set here or in any ancestor/enclosing module or class,
|
92
|
+
# as long as the defaults or their constants can be seen from this POV.
|
93
|
+
#
|
94
|
+
# Default class MUST be a descendant of Hash or respond to hash methods !!!
|
95
|
+
#
|
96
|
+
# For backend, use :libxml, :nokogiri, :ox, :rexml, or anything else, if you want it to always default
|
97
|
+
# to something other than the fastest backend found.
|
98
|
+
# Using nil will let the SaxParser decide.
|
99
|
+
@parser_defaults = {
|
100
|
+
:default_class => Hash,
|
101
|
+
:backend => nil,
|
102
|
+
:text_label => 'text',
|
103
|
+
:tag_translation => lambda {|txt| txt.gsub(/\-/, '_').downcase},
|
104
|
+
:shared_variable_name => 'attributes',
|
105
|
+
:templates => {},
|
106
|
+
:template_prefix => nil
|
107
|
+
}
|
108
|
+
|
109
|
+
# Merge any upper-level default definitions
|
110
|
+
if defined? PARSER_DEFAULTS
|
111
|
+
tmp_defaults = PARSER_DEFAULTS.dup
|
112
|
+
PARSER_DEFAULTS.replace(@parser_defaults).merge!(tmp_defaults)
|
113
|
+
else
|
114
|
+
PARSER_DEFAULTS = @parser_defaults
|
115
|
+
end
|
116
|
+
|
117
|
+
# Convert defaults to constants, available to all sub classes/modules/instances.
|
118
|
+
PARSER_DEFAULTS.each do |k, v|
|
119
|
+
k = k.to_s.upcase
|
120
|
+
#(const_set k, v) unless eval("defined? #{k}") #(const_defined?(k) or defined?(k))
|
121
|
+
if eval("defined? #{k}")
|
122
|
+
(const_set k, eval(k))
|
123
|
+
else
|
124
|
+
(const_set k, v)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
::Object::ATTACH_OBJECT_DEFAULT_OPTIONS = {
|
129
|
+
:shared_variable_name => SHARED_VARIABLE_NAME,
|
130
|
+
:default_class => DEFAULT_CLASS,
|
131
|
+
:text_label => TEXT_LABEL,
|
132
|
+
:create_accessors => [] #:all, :private, :shared, :hash
|
133
|
+
}
|
134
|
+
|
135
|
+
def self.parse(*args)
|
136
|
+
Handler.build(*args)
|
137
|
+
end
|
138
|
+
|
139
|
+
# A Cursor instance is created for each element encountered in the parsing run
|
140
|
+
# and is where the parsing result is constructed from the custom parsing template.
|
141
|
+
# The cursor is the glue between the handler and the resulting object build. The
|
142
|
+
# cursor receives input from the handler, loads the corresponding template data,
|
143
|
+
# and manipulates the incoming xml data to build the resulting object.
|
144
|
+
#
|
145
|
+
# Each cursor is added to the stack when its element begins, and removed from
|
146
|
+
# the stack when its element ends. The cursor tracks all the important objects
|
147
|
+
# necessary to build the resulting object. If you call #cursor on the handler,
|
148
|
+
# you will always get the last object added to the stack. Think of a cursor as
|
149
|
+
# a framework of tools that accompany each element's build process.
|
150
|
+
class Cursor
|
151
|
+
extend Forwardable
|
152
|
+
|
153
|
+
# model - currently active model (rename to current_model)
|
154
|
+
# local_model - model of this cursor's tag (rename to local_model)
|
155
|
+
# newtag - incoming tag of yet-to-be-created cursor. Get rid of this if you can.
|
156
|
+
# element_attachment_prefs - local object's attachment prefs based on local_model and current_model.
|
157
|
+
# level - cursor depth
|
158
|
+
attr_accessor :model, :local_model, :object, :tag, :handler, :parent, :level, :element_attachment_prefs, :new_element_callback, :initial_attributes #, :newtag
|
159
|
+
|
160
|
+
|
161
|
+
#SaxParser.install_defaults(self)
|
162
|
+
|
163
|
+
def_delegators :handler, :top, :stack
|
164
|
+
|
165
|
+
# Main get-constant method
|
166
|
+
def self.get_constant(klass)
|
167
|
+
#puts "Getting constant '#{klass.to_s}'"
|
168
|
+
|
169
|
+
case
|
170
|
+
when klass.is_a?(Class); klass
|
171
|
+
#when (klass=klass.to_s) == ''; DEFAULT_CLASS
|
172
|
+
when klass.nil?; DEFAULT_CLASS
|
173
|
+
when klass == ''; DEFAULT_CLASS
|
174
|
+
when klass[/::/]; eval(klass)
|
175
|
+
when defined?(klass); const_get(klass) ## == 'constant'; const_get(klass)
|
176
|
+
#when defined?(klass); eval(klass) # This was for 'element_handler' pattern.
|
177
|
+
else
|
178
|
+
Rfm.log.warn "Could not find constant '#{klass}'"
|
179
|
+
DEFAULT_CLASS
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def initialize(_tag, _handler, _parent=nil, _initial_attributes=nil) #, caller_binding=nil)
|
184
|
+
#def initialize(_model, _obj, _tag, _handler)
|
185
|
+
@tag = _tag
|
186
|
+
@handler = _handler
|
187
|
+
@parent = _parent || self
|
188
|
+
@initial_attributes = _initial_attributes
|
189
|
+
@level = @parent.level.to_i + 1
|
190
|
+
@local_model = (model_elements?(@tag, @parent.model) || DEFAULT_CLASS.new)
|
191
|
+
@element_attachment_prefs = attachment_prefs(@parent.model, @local_model, 'element')
|
192
|
+
#@attribute_attachment_prefs = attachment_prefs(@parent.model, @local_model, 'attribute')
|
193
|
+
|
194
|
+
if @element_attachment_prefs.is_a? Array
|
195
|
+
@new_element_callback = @element_attachment_prefs[1..-1]
|
196
|
+
@element_attachment_prefs = @element_attachment_prefs[0]
|
197
|
+
if @element_attachment_prefs.to_s == 'default'
|
198
|
+
@element_attachment_prefs = nil
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
#puts ["\nINITIALIZE_CURSOR tag: #{@tag}", "parent.object: #{@parent.object.class}", "local_model: #{@local_model.class}", "el_prefs: #{@element_attachment_prefs}", "new_el_callback: #{@new_element_callback}", "attributes: #{@initial_attributes}"]
|
203
|
+
|
204
|
+
self
|
205
|
+
end
|
206
|
+
|
207
|
+
|
208
|
+
##### SAX METHODS #####
|
209
|
+
|
210
|
+
# Receive a single attribute (any named attribute or text)
|
211
|
+
def receive_attribute(name, value)
|
212
|
+
#puts ["\nRECEIVE_ATTR '#{name}'", "value: #{value}", "tag: #{@tag}", "object: #{object.class}", "model: #{model['name']}"]
|
213
|
+
new_att = {name=>value} #.new.tap{|att| att[name]=value}
|
214
|
+
assign_attributes(new_att) #, @object, @model, @local_model)
|
215
|
+
rescue
|
216
|
+
Rfm.log.warn "Error: could not assign attribute '#{name.to_s}' to element '#{self.tag.to_s}': #{$!}"
|
217
|
+
end
|
218
|
+
|
219
|
+
def receive_start_element(_tag, _attributes)
|
220
|
+
#puts ["\nRECEIVE_START '#{_tag}'", "current_object: #{@object.class}", "current_model: #{@model['name']}", "attributes #{_attributes}"]
|
221
|
+
new_cursor = Cursor.new(_tag, @handler, self, _attributes) #, binding)
|
222
|
+
new_cursor.process_new_element(binding)
|
223
|
+
new_cursor
|
224
|
+
end # receive_start_element
|
225
|
+
|
226
|
+
# Decides how to attach element & attributes associated with this cursor.
|
227
|
+
def process_new_element(caller_binding=binding)
|
228
|
+
#puts ["\nPROCESS_NEW_ELEMENT tag: #{@tag}", "@element_attachment_prefs: #{@element_attachment_prefs}", "@local_model: #{local_model}"]
|
229
|
+
|
230
|
+
new_element = @new_element_callback ? get_callback(@new_element_callback, caller_binding) : nil
|
231
|
+
|
232
|
+
case
|
233
|
+
# when inital cursor, just set model & object.
|
234
|
+
when @tag == '__TOP__';
|
235
|
+
#puts "__TOP__"
|
236
|
+
@model = @handler.template
|
237
|
+
@object = @handler.initial_object
|
238
|
+
|
239
|
+
when @element_attachment_prefs == 'none';
|
240
|
+
#puts "__NONE__"
|
241
|
+
@model = @parent.model #nil
|
242
|
+
@object = @parent.object #nil
|
243
|
+
|
244
|
+
if @initial_attributes && @initial_attributes.any? #&& @attribute_attachment_prefs != 'none'
|
245
|
+
assign_attributes(@initial_attributes) #, @object, @model, @local_model)
|
246
|
+
end
|
247
|
+
|
248
|
+
when @element_attachment_prefs == 'cursor';
|
249
|
+
#puts "__CURSOR__"
|
250
|
+
@model = @local_model
|
251
|
+
@object = new_element || DEFAULT_CLASS.allocate
|
252
|
+
|
253
|
+
if @initial_attributes && @initial_attributes.any? #&& @attribute_attachment_prefs != 'none'
|
254
|
+
assign_attributes(@initial_attributes) #, @object, @model, @local_model)
|
255
|
+
end
|
256
|
+
|
257
|
+
else
|
258
|
+
#puts "__OTHER__"
|
259
|
+
@model = @local_model
|
260
|
+
@object = new_element || DEFAULT_CLASS.allocate
|
261
|
+
|
262
|
+
if @initial_attributes && @initial_attributes.any? #&& @attribute_attachment_prefs != 'none'
|
263
|
+
#puts "PROCESS_NEW_ELEMENT calling assign_attributes with ATTRIBUTES #{@initial_attributes}"
|
264
|
+
assign_attributes(@initial_attributes) #, @object, @model, @local_model)
|
265
|
+
end
|
266
|
+
|
267
|
+
# If @local_model has a delimiter, defer attach_new_element until later.
|
268
|
+
#puts "PROCESS_NEW_ELEMENT delimiter of @local_model #{delimiter?(@local_model)}"
|
269
|
+
if !delimiter?(@local_model)
|
270
|
+
#attach_new_object(@parent.object, @object, @tag, @parent.model, @local_model, 'element')
|
271
|
+
#puts "PROCESS_NEW_ELEMENT calling attach_new_element with TAG #{@tag} and OBJECT #{@object}"
|
272
|
+
attach_new_element(@tag, @object)
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
self
|
277
|
+
end
|
278
|
+
|
279
|
+
|
280
|
+
def receive_end_element(_tag)
|
281
|
+
#puts ["\nRECEIVE_END_ELEMENT '#{_tag}'", "tag: #{@tag}", "object: #{@object.class}", "model: #{@model['name']}", "local_model: #{@local_model['name']}"]
|
282
|
+
#puts ["\nEND_ELEMENT_OBJECT", object.to_yaml]
|
283
|
+
begin
|
284
|
+
|
285
|
+
if _tag == @tag && (@model == @local_model)
|
286
|
+
# Data cleaup
|
287
|
+
compactor_settings = compact? || compact?(top.model)
|
288
|
+
#(compactor_settings = compact?(top.model)) unless compactor_settings # prefer local settings, or use top settings.
|
289
|
+
(clean_members {|v| clean_members(v){|w| clean_members(w)}}) if compactor_settings
|
290
|
+
end
|
291
|
+
|
292
|
+
if (delimiter = delimiter?(@local_model); delimiter && !['none','cursor'].include?(@element_attachment_prefs.to_s))
|
293
|
+
#attach_new_object(@parent.object, @object, @tag, @parent.model, @local_model, 'element')
|
294
|
+
#puts "RECEIVE_END_ELEMENT attaching new element TAG (#{@tag}) OBJECT (#{@object.class}) #{@object.to_yaml} WITH LOCAL MODEL #{@local_model.to_yaml} TO PARENT (#{@parent.object.class}) #{@parent.object.to_yaml} PARENT MODEL #{@parent.model.to_yaml}"
|
295
|
+
attach_new_element(@tag, @object)
|
296
|
+
end
|
297
|
+
|
298
|
+
if _tag == @tag #&& (@model == @local_model)
|
299
|
+
# End-element callbacks.
|
300
|
+
#run_callback(_tag, self)
|
301
|
+
callback = before_close?(@local_model)
|
302
|
+
get_callback(callback, binding) if callback
|
303
|
+
end
|
304
|
+
|
305
|
+
if _tag == @tag
|
306
|
+
# return true only if matching tags
|
307
|
+
return true
|
308
|
+
end
|
309
|
+
|
310
|
+
# # return true only if matching tags
|
311
|
+
# if _tag == @tag
|
312
|
+
# return true
|
313
|
+
# end
|
314
|
+
|
315
|
+
return
|
316
|
+
# rescue
|
317
|
+
# Rfm.log.debug "Error: end_element tag '#{_tag}' failed: #{$!}"
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
### Parse callback instructions, compile & send callback method ###
|
322
|
+
### TODO: This is way too convoluted. Document it better, or refactor!!!
|
323
|
+
# This method will send a method to an object, with parameters, and return a new object.
|
324
|
+
# Input (first param): string, symbol, or array of strings
|
325
|
+
# Returns: object
|
326
|
+
# Default options:
|
327
|
+
# :object=>object
|
328
|
+
# :method=>'a method name string or symbol'
|
329
|
+
# :params=>"params string to be eval'd in context of cursor"
|
330
|
+
# Usage:
|
331
|
+
# callback: send a method (or eval string) to an object with parameters, consisting of...
|
332
|
+
# string: a string to be eval'd in context of current object.
|
333
|
+
# or symbol: method to be called on current object.
|
334
|
+
# or array: object, method, params.
|
335
|
+
# object: <object or string>
|
336
|
+
# method: <string or symbol>
|
337
|
+
# params: <string>
|
338
|
+
#
|
339
|
+
# TODO-MAYBE: Change param order to (method, object, params),
|
340
|
+
# might help confusion with param complexities.
|
341
|
+
#
|
342
|
+
def get_callback(callback, caller_binding=binding, defaults={})
|
343
|
+
input = callback.is_a?(Array) ? callback.dup : callback
|
344
|
+
#puts "\nGET_CALLBACK tag: #{tag}, callback: #{callback}"
|
345
|
+
params = case
|
346
|
+
when input.is_a?(String) || input.is_a?(Symbol)
|
347
|
+
[nil, input]
|
348
|
+
# when input.is_a?(Symbol)
|
349
|
+
# [nil, input]
|
350
|
+
when input.is_a?(Array)
|
351
|
+
#puts ["\nCURSOR#get_callback is an array", input]
|
352
|
+
case
|
353
|
+
when input[0].is_a?(Symbol)
|
354
|
+
[nil, input].flatten(1)
|
355
|
+
when input[1].is_a?(String) && ( input.size > 2 || (remove_colon=(input[1][0,1]==":"); remove_colon) )
|
356
|
+
code_or_method = input[1].dup
|
357
|
+
code_or_method[0]='' if remove_colon
|
358
|
+
code_or_method = code_or_method.to_sym
|
359
|
+
output = [input[0], code_or_method, input[2..-1]].flatten(1)
|
360
|
+
#puts ["\nCURSOR#get_callback converted input[1] to symbol", output]
|
361
|
+
output
|
362
|
+
else # when input is ['object', 'sym-or-str', 'param1',' param2', ...]
|
363
|
+
input
|
364
|
+
end
|
365
|
+
else
|
366
|
+
[]
|
367
|
+
end
|
368
|
+
|
369
|
+
obj_raw = params.shift
|
370
|
+
#puts ["\nOBJECT_RAW:","class: #{obj_raw.class}", "object: #{obj_raw}"]
|
371
|
+
obj = if obj_raw.is_a?(String)
|
372
|
+
eval(obj_raw.to_s, caller_binding)
|
373
|
+
else
|
374
|
+
obj_raw
|
375
|
+
end
|
376
|
+
if obj.nil? || obj == ''
|
377
|
+
obj = defaults[:object] || @object
|
378
|
+
end
|
379
|
+
#puts ["\nOBJECT:","class: #{obj.class}", "object: #{obj}"]
|
380
|
+
|
381
|
+
code = params.shift || defaults[:method]
|
382
|
+
params.each_with_index do |str, i|
|
383
|
+
if str.is_a?(String)
|
384
|
+
params[i] = eval(str, caller_binding)
|
385
|
+
end
|
386
|
+
end
|
387
|
+
params = defaults[:params] if params.size == 0
|
388
|
+
#puts ["\nGET_CALLBACK tag: #{@tag}" ,"callback: #{callback}", "obj.class: #{obj.class}", "code: #{code}", "params-class #{params.class}"]
|
389
|
+
case
|
390
|
+
when (code.nil? || code=='')
|
391
|
+
obj
|
392
|
+
when (code.is_a?(Symbol) || params)
|
393
|
+
#puts ["\nGET_CALLBACK sending symbol", obj.class, code]
|
394
|
+
obj.send(*[code, params].flatten(1).compact)
|
395
|
+
when code.is_a?(String)
|
396
|
+
#puts ["\nGET_CALLBACK evaling string", obj.class, code]
|
397
|
+
obj.send :eval, code
|
398
|
+
#eval(code, caller_binding)
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
# # Run before-close callback.
|
403
|
+
# def run_callback(_tag, _cursor=self, _model=_cursor.local_model, _object=_cursor.object )
|
404
|
+
# callback = before_close?(_model)
|
405
|
+
# #puts ["\nRUN_CALLBACK", _tag, _cursor.tag, _object.class, callback, callback.class]
|
406
|
+
# if callback.is_a? Symbol
|
407
|
+
# _object.send callback, _cursor
|
408
|
+
# elsif callback.is_a?(String)
|
409
|
+
# _object.send :eval, callback
|
410
|
+
# end
|
411
|
+
# end
|
412
|
+
|
413
|
+
|
414
|
+
|
415
|
+
|
416
|
+
##### MERGE METHODS #####
|
417
|
+
|
418
|
+
# Assign attributes to element.
|
419
|
+
def assign_attributes(_attributes)
|
420
|
+
if _attributes && !_attributes.empty?
|
421
|
+
|
422
|
+
_attributes.each do |k,v|
|
423
|
+
#attach_new_object(base_object, v, k, base_model, model_attributes?(k, new_model), 'attribute')}
|
424
|
+
attr_model = model_attributes?(k, @local_model)
|
425
|
+
|
426
|
+
label = label_or_tag(k, attr_model)
|
427
|
+
|
428
|
+
prefs = [attachment_prefs(@model, attr_model, 'attribute')].flatten(1)[0]
|
429
|
+
|
430
|
+
shared_var_name = shared_variable_name(prefs)
|
431
|
+
(prefs = "shared") if shared_var_name
|
432
|
+
|
433
|
+
# Use local create_accessors prefs first, then more general ones.
|
434
|
+
create_accessors = accessor?(attr_model) || create_accessors?(@model)
|
435
|
+
#(create_accessors = create_accessors?(@model)) unless create_accessors && create_accessors.any?
|
436
|
+
|
437
|
+
#puts ["\nATTACH_NEW_OBJECT 1", "type: #{type}", "label: #{label}", "base_object: #{base_object.class}", "new_object: #{new_object.class}", "delimiter: #{delimiter?(new_model)}", "prefs: #{prefs}", "shared_var_name: #{shared_var_name}", "create_accessors: #{create_accessors}"]
|
438
|
+
@object._attach_object!(v, label, delimiter?(attr_model), prefs, 'attribute', :default_class=>DEFAULT_CLASS, :shared_variable_name=>shared_var_name, :create_accessors=>create_accessors)
|
439
|
+
end
|
440
|
+
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
# def attach_new_object(base_object, new_object, name, base_model, new_model, type)
|
445
|
+
# label = label_or_tag(name, new_model)
|
446
|
+
#
|
447
|
+
# # Was this, which works fine, but not as efficient:
|
448
|
+
# # prefs = [attachment_prefs(base_model, new_model, type)].flatten(1)[0]
|
449
|
+
# prefs = if type=='attribute'
|
450
|
+
# [attachment_prefs(base_model, new_model, type)].flatten(1)[0]
|
451
|
+
# else
|
452
|
+
# @element_attachment_prefs
|
453
|
+
# end
|
454
|
+
#
|
455
|
+
# shared_var_name = shared_variable_name(prefs)
|
456
|
+
# (prefs = "shared") if shared_var_name
|
457
|
+
#
|
458
|
+
# # Use local create_accessors prefs first, then more general ones.
|
459
|
+
# create_accessors = accessor?(new_model)
|
460
|
+
# (create_accessors = create_accessors?(base_model)) unless create_accessors && create_accessors.any?
|
461
|
+
#
|
462
|
+
# # # This is NEW!
|
463
|
+
# # translator = new_model['translator']
|
464
|
+
# # if translator
|
465
|
+
# # new_object = base_object.send translator, name, new_object
|
466
|
+
# # end
|
467
|
+
#
|
468
|
+
#
|
469
|
+
# #puts ["\nATTACH_NEW_OBJECT 1", "type: #{type}", "label: #{label}", "base_object: #{base_object.class}", "new_object: #{new_object.class}", "delimiter: #{delimiter?(new_model)}", "prefs: #{prefs}", "shared_var_name: #{shared_var_name}", "create_accessors: #{create_accessors}"]
|
470
|
+
# base_object._attach_object!(new_object, label, delimiter?(new_model), prefs, type, :default_class=>DEFAULT_CLASS, :shared_variable_name=>shared_var_name, :create_accessors=>create_accessors)
|
471
|
+
# #puts ["\nATTACH_NEW_OBJECT 2: #{base_object.class} with ", label, delimiter?(new_model), prefs, type, :shared_variable_name=>shared_var_name, :create_accessors=>create_accessors]
|
472
|
+
# # if type == 'attribute'
|
473
|
+
# # puts ["\nATTACH_ATTR", "name: #{name}", "label: #{label}", "new_object: #{new_object.class rescue ''}", "base_object: #{base_object.class rescue ''}", "base_model: #{base_model['name'] rescue ''}", "new_model: #{new_model['name'] rescue ''}", "prefs: #{prefs}"]
|
474
|
+
# # end
|
475
|
+
# end
|
476
|
+
|
477
|
+
def attach_new_element(name, new_object) #old params (base_object, new_object, name, base_model, new_model, type)
|
478
|
+
label = label_or_tag(name, @local_model)
|
479
|
+
|
480
|
+
# Was this, which works fine, but not as efficient:
|
481
|
+
# prefs = [attachment_prefs(base_model, new_model, type)].flatten(1)[0]
|
482
|
+
prefs = @element_attachment_prefs
|
483
|
+
|
484
|
+
shared_var_name = shared_variable_name(prefs)
|
485
|
+
(prefs = "shared") if shared_var_name
|
486
|
+
|
487
|
+
# Use local create_accessors prefs first, then more general ones.
|
488
|
+
create_accessors = accessor?(@local_model) || create_accessors?(@parent.model)
|
489
|
+
#(create_accessors = create_accessors?(@parent.model)) unless create_accessors && create_accessors.any?
|
490
|
+
|
491
|
+
# # This is NEW!
|
492
|
+
# translator = new_model['translator']
|
493
|
+
# if translator
|
494
|
+
# new_object = base_object.send translator, name, new_object
|
495
|
+
# end
|
496
|
+
|
497
|
+
|
498
|
+
#puts ["\nATTACH_NEW_ELEMENT 1", "new_object: #{new_object}", "parent_object: #{@parent.object}", "label: #{label}", "delimiter: #{delimiter?(@local_model)}", "prefs: #{prefs}", "shared_var_name: #{shared_var_name}", "create_accessors: #{create_accessors}"]
|
499
|
+
@parent.object._attach_object!(new_object, label, delimiter?(@local_model), prefs, 'element', :default_class=>DEFAULT_CLASS, :shared_variable_name=>shared_var_name, :create_accessors=>create_accessors)
|
500
|
+
# if type == 'attribute'
|
501
|
+
# puts ["\nATTACH_ATTR", "name: #{name}", "label: #{label}", "new_object: #{new_object.class rescue ''}", "base_object: #{base_object.class rescue ''}", "base_model: #{base_model['name'] rescue ''}", "new_model: #{new_model['name'] rescue ''}", "prefs: #{prefs}"]
|
502
|
+
# end
|
503
|
+
end
|
504
|
+
|
505
|
+
def attachment_prefs(base_model, new_model, type)
|
506
|
+
case type
|
507
|
+
when 'element'; attach?(new_model) || attach_elements?(base_model) #|| attach?(top.model) || attach_elements?(top.model)
|
508
|
+
when 'attribute'; attach?(new_model) || attach_attributes?(base_model) #|| attach?(top.model) || attach_attributes?(top.model)
|
509
|
+
end
|
510
|
+
end
|
511
|
+
|
512
|
+
def shared_variable_name(prefs)
|
513
|
+
rslt = nil
|
514
|
+
if prefs.to_s[0,1] == "_"
|
515
|
+
rslt = prefs.to_s[1..-1] #prefs.gsub(/^_/, '')
|
516
|
+
end
|
517
|
+
end
|
518
|
+
|
519
|
+
|
520
|
+
##### UTILITY #####
|
521
|
+
|
522
|
+
def get_constant(klass)
|
523
|
+
self.class.get_constant(klass)
|
524
|
+
end
|
525
|
+
|
526
|
+
# Methods for current _model
|
527
|
+
def ivg(name, _object=@object); _object.instance_variable_get "@#{name}"; end
|
528
|
+
def ivs(name, value, _object=@object); _object.instance_variable_set "@#{name}", value; end
|
529
|
+
def model_elements?(which=nil, _model=@model); _model && _model.has_key?('elements') && ((_model['elements'] && which) ? _model['elements'].find{|e| e['name']==which} : _model['elements']) ; end
|
530
|
+
def model_attributes?(which=nil, _model=@model); _model && _model.has_key?('attributes') && ((_model['attributes'] && which) ? _model['attributes'].find{|a| a['name']==which} : _model['attributes']) ; end
|
531
|
+
def depth?(_model=@model); _model && _model['depth']; end
|
532
|
+
def before_close?(_model=@model); _model && _model['before_close']; end
|
533
|
+
def each_before_close?(_model=@model); _model && _model['each_before_close']; end
|
534
|
+
def compact?(_model=@model); _model && _model['compact']; end
|
535
|
+
def attach?(_model=@model); _model && _model['attach']; end
|
536
|
+
def attach_elements?(_model=@model); _model && _model['attach_elements']; end
|
537
|
+
def attach_attributes?(_model=@model); _model && _model['attach_attributes']; end
|
538
|
+
def delimiter?(_model=@model); _model && _model['delimiter']; end
|
539
|
+
def as_name?(_model=@model); _model && _model['as_name']; end
|
540
|
+
def initialize_with?(_model=@model); _model && _model['initialize_with']; end
|
541
|
+
def create_accessors?(_model=@model); _model && _model['create_accessors'] && [_model['create_accessors']].flatten.compact; end
|
542
|
+
def accessor?(_model=@model); _model && _model['accessor'] && [_model['accessor']].flatten.compact; end
|
543
|
+
def element_handler?(_model=@model); _model && _model['element_handler']; end
|
544
|
+
|
545
|
+
|
546
|
+
# Methods for submodel
|
547
|
+
|
548
|
+
# This might be broken.
|
549
|
+
def label_or_tag(_tag=@tag, new_model=@local_model); as_name?(new_model) || _tag; end
|
550
|
+
|
551
|
+
|
552
|
+
def clean_members(obj=@object)
|
553
|
+
#puts ["CURSOR.clean_members: #{object.class}", "tag: #{tag}", "model-name: #{model[:name]}"]
|
554
|
+
# cursor.object = clean_member(cursor.object)
|
555
|
+
# clean_members(ivg(shared_attribute_var, obj))
|
556
|
+
if obj.is_a?(Hash)
|
557
|
+
obj.dup.each do |k,v|
|
558
|
+
obj[k] = clean_member(v)
|
559
|
+
yield(v) if block_given?
|
560
|
+
end
|
561
|
+
elsif obj.is_a?(Array)
|
562
|
+
obj.dup.each_with_index do |v,i|
|
563
|
+
obj[i] = clean_member(v)
|
564
|
+
yield(v) if block_given?
|
565
|
+
end
|
566
|
+
else
|
567
|
+
obj.instance_variables.each do |var|
|
568
|
+
dat = obj.instance_variable_get(var)
|
569
|
+
obj.instance_variable_set(var, clean_member(dat))
|
570
|
+
yield(dat) if block_given?
|
571
|
+
end
|
572
|
+
end
|
573
|
+
# obj.instance_variables.each do |var|
|
574
|
+
# dat = obj.instance_variable_get(var)
|
575
|
+
# obj.instance_variable_set(var, clean_member(dat))
|
576
|
+
# yield(dat) if block_given?
|
577
|
+
# end
|
578
|
+
end
|
579
|
+
|
580
|
+
def clean_member(val)
|
581
|
+
if val.is_a?(Hash) || val.is_a?(Array);
|
582
|
+
if val && val.empty?
|
583
|
+
nil
|
584
|
+
elsif val && val.respond_to?(:values) && val.size == 1
|
585
|
+
val.values[0]
|
368
586
|
else
|
369
|
-
|
587
|
+
val
|
370
588
|
end
|
371
|
-
|
372
|
-
|
373
|
-
#
|
374
|
-
|
375
|
-
|
376
|
-
#
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
#puts ["\nGET_CALLBACK tag: #{@tag}" ,"callback: #{callback}", "obj.class: #{obj.class}", "code: #{code}", "params-class #{params.class}"]
|
382
|
-
case
|
383
|
-
when (code.nil? || code=='')
|
384
|
-
obj
|
385
|
-
when (code.is_a?(Symbol) || params)
|
386
|
-
#puts ["\nGET_CALLBACK sending symbol", obj.class, code]
|
387
|
-
obj.send *[code, params].flatten(1).compact
|
388
|
-
when code.is_a?(String)
|
389
|
-
#puts ["\nGET_CALLBACK evaling string", obj.class, code]
|
390
|
-
obj.send :eval, code
|
391
|
-
#eval(code, caller_binding)
|
392
|
-
end
|
589
|
+
else
|
590
|
+
val
|
591
|
+
# # Probably shouldn't do this on instance-var values. ...Why not?
|
592
|
+
# if val.instance_variables.size < 1
|
593
|
+
# nil
|
594
|
+
# elsif val.instance_variables.size == 1
|
595
|
+
# val.instance_variable_get(val.instance_variables[0])
|
596
|
+
# else
|
597
|
+
# val
|
598
|
+
# end
|
393
599
|
end
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
600
|
+
end
|
601
|
+
|
602
|
+
end # Cursor
|
603
|
+
|
604
|
+
|
605
|
+
|
606
|
+
##### SAX HANDLER #####
|
607
|
+
|
608
|
+
|
609
|
+
# A handler instance is created for each parsing run. The handler has several important functions:
|
610
|
+
# 1. Receive callbacks from the sax/stream parsing engine (start_element, end_element, attribute...).
|
611
|
+
# 2. Maintain a stack of cursors, growing & shrinking, throughout the parsing run.
|
612
|
+
# 3. Maintain a Cursor instance throughout the parsing run.
|
613
|
+
# 3. Hand over parser callbacks & data to the Cursor instance for refined processing.
|
614
|
+
#
|
615
|
+
# The handler instance is unique to each different parsing gem but inherits generic
|
616
|
+
# methods from this Handler module. During each parsing run, the Hander module creates
|
617
|
+
# a new instance of the spcified parer's handler class and runs the handler's main parsing method.
|
618
|
+
# At the end of the parsing run the handler instance, along with it's newly parsed object,
|
619
|
+
# is returned to the object that originally called for the parsing run (your script/app/whatever).
|
620
|
+
module Handler
|
621
|
+
|
622
|
+
attr_accessor :stack, :template, :initial_object, :stack_debug
|
623
|
+
|
624
|
+
#SaxParser.install_defaults(self)
|
625
|
+
|
626
|
+
|
627
|
+
### Class Methods ###
|
628
|
+
|
629
|
+
# Main parsing interface (also aliased at SaxParser.parse)
|
630
|
+
def self.build(io, template=nil, initial_object=nil, parser=nil, options={})
|
631
|
+
parser = parser || options[:parser] || BACKEND
|
632
|
+
parser = get_backend(parser)
|
633
|
+
(Rfm.log.info "Using backend parser: #{parser}, with template: #{template}") if options[:log_parser]
|
634
|
+
parser.build(io, template, initial_object)
|
635
|
+
end
|
636
|
+
|
637
|
+
def self.included(base)
|
638
|
+
# Add a .build method to the custom handler instance, when the generic Handler module is included.
|
639
|
+
def base.build(io, template=nil, initial_object=nil)
|
640
|
+
handler = new(template, initial_object)
|
641
|
+
handler.run_parser(io)
|
642
|
+
handler
|
643
|
+
end
|
644
|
+
end # self.included
|
645
|
+
|
646
|
+
# Takes backend symbol and returns custom Handler class for specified backend.
|
647
|
+
def self.get_backend(parser=BACKEND)
|
648
|
+
(parser = decide_backend) unless parser
|
649
|
+
if parser.is_a?(String) || parser.is_a?(Symbol)
|
650
|
+
parser_proc = PARSERS[parser.to_sym][:proc]
|
651
|
+
parser_proc.call unless parser_proc.nil? || const_defined?((parser.to_s.capitalize + 'Handler').to_sym)
|
652
|
+
SaxParser.const_get(parser.to_s.capitalize + "Handler")
|
653
|
+
end
|
654
|
+
rescue
|
655
|
+
raise "Could not load the backend parser '#{parser}': #{$!}"
|
656
|
+
end
|
657
|
+
|
658
|
+
# Finds a loadable backend and returns its symbol.
|
659
|
+
def self.decide_backend
|
660
|
+
#BACKENDS.find{|b| !Gem::Specification::find_all_by_name(b[1]).empty? || b[0]==:rexml}[0]
|
661
|
+
PARSERS.find{|k,v| !Gem::Specification::find_all_by_name(v[:file]).empty? || k == :rexml}[0]
|
662
|
+
rescue
|
663
|
+
raise "The xml parser could not find a loadable backend library: #{$!}"
|
664
|
+
end
|
665
|
+
|
666
|
+
|
667
|
+
|
668
|
+
### Instance Methods ###
|
669
|
+
|
670
|
+
def initialize(_template=nil, _initial_object=nil)
|
671
|
+
@initial_object = case
|
672
|
+
when _initial_object.nil?; DEFAULT_CLASS.new
|
673
|
+
when _initial_object.is_a?(Class); _initial_object.new
|
674
|
+
when _initial_object.is_a?(String) || _initial_object.is_a?(Symbol); SaxParser.get_constant(_initial_object).new
|
675
|
+
else _initial_object
|
676
|
+
end
|
677
|
+
@stack = []
|
678
|
+
@stack_debug=[]
|
679
|
+
@template = get_template(_template)
|
680
|
+
set_cursor Cursor.new('__TOP__', self).process_new_element
|
681
|
+
end
|
682
|
+
|
683
|
+
# Takes string, symbol, hash, and returns a (possibly cached) parsing template.
|
684
|
+
# String can be a file name, yaml, xml.
|
685
|
+
# Symbol is a name of a template stored in SaxParser@templates (you would set the templates when your app or gem loads).
|
686
|
+
# Templates stored in the SaxParser@templates var can be strings of code, file specs, or hashes.
|
687
|
+
def get_template(name)
|
688
|
+
# dat = templates[name]
|
689
|
+
# if dat
|
690
|
+
# rslt = load_template(dat)
|
691
|
+
# else
|
692
|
+
# rslt = load_template(name)
|
403
693
|
# end
|
404
|
-
#
|
694
|
+
# (templates[name] = rslt) #unless dat == rslt
|
695
|
+
# The above works, but this is cleaner.
|
696
|
+
TEMPLATES[name] = TEMPLATES[name] && load_template(TEMPLATES[name]) || load_template(name)
|
697
|
+
end
|
405
698
|
|
699
|
+
# Does the heavy-lifting of template retrieval.
|
700
|
+
def load_template(dat)
|
701
|
+
#puts "DAT: #{dat}, class #{dat.class}"
|
702
|
+
prefix = defined?(TEMPLATE_PREFIX) ? TEMPLATE_PREFIX : ''
|
703
|
+
#puts "SaxParser::Handler#load_template... 'prefix' is #{prefix}"
|
704
|
+
rslt = case
|
705
|
+
when dat.is_a?(Hash); dat
|
706
|
+
when (dat.is_a?(String) && dat[/^\//]); YAML.load_file dat
|
707
|
+
when dat.to_s[/\.y.?ml$/i]; (YAML.load_file(File.join(*[prefix, dat].compact)))
|
708
|
+
# This line might cause an infinite loop.
|
709
|
+
when dat.to_s[/\.xml$/i]; self.class.build(File.join(*[prefix, dat].compact), nil, {'compact'=>true})
|
710
|
+
when dat.to_s[/^<.*>/i]; "Convert from xml to Hash - under construction"
|
711
|
+
when dat.is_a?(String); YAML.load dat
|
712
|
+
else DEFAULT_CLASS.new
|
713
|
+
end
|
714
|
+
#puts rslt
|
715
|
+
rslt
|
716
|
+
end
|
406
717
|
|
718
|
+
def result
|
719
|
+
stack[0].object if stack[0].is_a? Cursor
|
720
|
+
end
|
407
721
|
|
722
|
+
def cursor
|
723
|
+
stack.last
|
724
|
+
end
|
408
725
|
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
# else
|
590
|
-
# val
|
591
|
-
# end
|
592
|
-
end
|
593
|
-
end
|
594
|
-
|
595
|
-
end # Cursor
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
##### SAX HANDLER #####
|
600
|
-
|
601
|
-
|
602
|
-
# A handler instance is created for each parsing run. The handler has several important functions:
|
603
|
-
# 1. Receive callbacks from the sax/stream parsing engine (start_element, end_element, attribute...).
|
604
|
-
# 2. Maintain a stack of cursors, growing & shrinking, throughout the parsing run.
|
605
|
-
# 3. Maintain a Cursor instance throughout the parsing run.
|
606
|
-
# 3. Hand over parser callbacks & data to the Cursor instance for refined processing.
|
607
|
-
#
|
608
|
-
# The handler instance is unique to each different parsing gem but inherits generic
|
609
|
-
# methods from this Handler module. During each parsing run, the Hander module creates
|
610
|
-
# a new instance of the spcified parer's handler class and runs the handler's main parsing method.
|
611
|
-
# At the end of the parsing run the handler instance, along with it's newly parsed object,
|
612
|
-
# is returned to the object that originally called for the parsing run (your script/app/whatever).
|
613
|
-
module Handler
|
614
|
-
|
615
|
-
attr_accessor :stack, :template, :initial_object, :stack_debug
|
616
|
-
|
617
|
-
#SaxParser.install_defaults(self)
|
618
|
-
|
619
|
-
|
620
|
-
### Class Methods ###
|
621
|
-
|
622
|
-
# Main parsing interface (also aliased at SaxParser.parse)
|
623
|
-
def self.build(io, template=nil, initial_object=nil, parser=nil, options={})
|
624
|
-
parser = parser || options[:parser] || BACKEND
|
625
|
-
parser = get_backend(parser)
|
626
|
-
(Rfm.log.info "Using backend parser: #{parser}, with template: #{template}") if options[:log_parser]
|
627
|
-
parser.build(io, template, initial_object)
|
628
|
-
end
|
629
|
-
|
630
|
-
def self.included(base)
|
631
|
-
# Add a .build method to the custom handler instance, when the generic Handler module is included.
|
632
|
-
def base.build(io, template=nil, initial_object=nil)
|
633
|
-
handler = new(template, initial_object)
|
634
|
-
handler.run_parser(io)
|
635
|
-
handler
|
636
|
-
end
|
637
|
-
end # self.included
|
638
|
-
|
639
|
-
# Takes backend symbol and returns custom Handler class for specified backend.
|
640
|
-
def self.get_backend(parser=BACKEND)
|
641
|
-
(parser = decide_backend) unless parser
|
642
|
-
if parser.is_a?(String) || parser.is_a?(Symbol)
|
643
|
-
parser_proc = PARSERS[parser.to_sym][:proc]
|
644
|
-
parser_proc.call unless parser_proc.nil? || const_defined?((parser.to_s.capitalize + 'Handler').to_sym)
|
645
|
-
SaxParser.const_get(parser.to_s.capitalize + "Handler")
|
646
|
-
end
|
647
|
-
rescue
|
648
|
-
raise "Could not load the backend parser '#{parser}': #{$!}"
|
649
|
-
end
|
650
|
-
|
651
|
-
# Finds a loadable backend and returns its symbol.
|
652
|
-
def self.decide_backend
|
653
|
-
#BACKENDS.find{|b| !Gem::Specification::find_all_by_name(b[1]).empty? || b[0]==:rexml}[0]
|
654
|
-
PARSERS.find{|k,v| !Gem::Specification::find_all_by_name(v[:file]).empty? || k == :rexml}[0]
|
655
|
-
rescue
|
656
|
-
raise "The xml parser could not find a loadable backend library: #{$!}"
|
657
|
-
end
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
### Instance Methods ###
|
662
|
-
|
663
|
-
def initialize(_template=nil, _initial_object=nil)
|
664
|
-
@initial_object = case
|
665
|
-
when _initial_object.nil?; DEFAULT_CLASS.new
|
666
|
-
when _initial_object.is_a?(Class); _initial_object.new
|
667
|
-
when _initial_object.is_a?(String) || _initial_object.is_a?(Symbol); SaxParser.get_constant(_initial_object).new
|
668
|
-
else _initial_object
|
669
|
-
end
|
670
|
-
@stack = []
|
671
|
-
@stack_debug=[]
|
672
|
-
@template = get_template(_template)
|
673
|
-
set_cursor Cursor.new('__TOP__', self).process_new_element
|
674
|
-
end
|
675
|
-
|
676
|
-
# Takes string, symbol, hash, and returns a (possibly cached) parsing template.
|
677
|
-
# String can be a file name, yaml, xml.
|
678
|
-
# Symbol is a name of a template stored in SaxParser@templates (you would set the templates when your app or gem loads).
|
679
|
-
# Templates stored in the SaxParser@templates var can be strings of code, file specs, or hashes.
|
680
|
-
def get_template(name)
|
681
|
-
# dat = templates[name]
|
682
|
-
# if dat
|
683
|
-
# rslt = load_template(dat)
|
684
|
-
# else
|
685
|
-
# rslt = load_template(name)
|
686
|
-
# end
|
687
|
-
# (templates[name] = rslt) #unless dat == rslt
|
688
|
-
# The above works, but this is cleaner.
|
689
|
-
TEMPLATES[name] = TEMPLATES[name] && load_template(TEMPLATES[name]) || load_template(name)
|
690
|
-
end
|
691
|
-
|
692
|
-
# Does the heavy-lifting of template retrieval.
|
693
|
-
def load_template(dat)
|
694
|
-
#puts "DAT: #{dat}, class #{dat.class}"
|
695
|
-
prefix = defined?(TEMPLATE_PREFIX) ? TEMPLATE_PREFIX : ''
|
696
|
-
#puts "SaxParser::Handler#load_template... 'prefix' is #{prefix}"
|
697
|
-
rslt = case
|
698
|
-
when dat.is_a?(Hash); dat
|
699
|
-
when (dat.is_a?(String) && dat[/^\//]); YAML.load_file dat
|
700
|
-
when dat.to_s[/\.y.?ml$/i]; (YAML.load_file(File.join(*[prefix, dat].compact)))
|
701
|
-
# This line might cause an infinite loop.
|
702
|
-
when dat.to_s[/\.xml$/i]; self.class.build(File.join(*[prefix, dat].compact), nil, {'compact'=>true})
|
703
|
-
when dat.to_s[/^<.*>/i]; "Convert from xml to Hash - under construction"
|
704
|
-
when dat.is_a?(String); YAML.load dat
|
705
|
-
else DEFAULT_CLASS.new
|
706
|
-
end
|
707
|
-
#puts rslt
|
708
|
-
rslt
|
709
|
-
end
|
710
|
-
|
711
|
-
def result
|
712
|
-
stack[0].object if stack[0].is_a? Cursor
|
713
|
-
end
|
714
|
-
|
715
|
-
def cursor
|
716
|
-
stack.last
|
717
|
-
end
|
718
|
-
|
719
|
-
def set_cursor(args) # cursor_object
|
720
|
-
if args.is_a? Cursor
|
721
|
-
stack.push(args)
|
722
|
-
#@stack_debug.push(args.dup.tap(){|c| c.handler = c.handler.object_id; c.parent = c.parent.tag})
|
723
|
-
end
|
724
|
-
cursor
|
725
|
-
end
|
726
|
-
|
727
|
-
def dump_cursor
|
728
|
-
stack.pop
|
729
|
-
end
|
730
|
-
|
731
|
-
def top
|
732
|
-
stack[0]
|
733
|
-
end
|
734
|
-
|
735
|
-
def transform(name)
|
736
|
-
return name unless TAG_TRANSLATION.is_a?(Proc)
|
737
|
-
TAG_TRANSLATION.call(name.to_s)
|
738
|
-
end
|
739
|
-
|
740
|
-
# Add a node to an existing element.
|
741
|
-
def _start_element(tag, attributes=nil, *args)
|
742
|
-
#puts ["_START_ELEMENT", tag, attributes, args].to_yaml # if tag.to_s.downcase=='fmrestulset'
|
743
|
-
tag = transform tag
|
744
|
-
if attributes
|
745
|
-
# This crazy thing transforms attribute keys to underscore (or whatever).
|
746
|
-
#attributes = default_class[*attributes.collect{|k,v| [transform(k),v] }.flatten]
|
747
|
-
# This works but downcases all attribute names - not good.
|
748
|
-
attributes = DEFAULT_CLASS.new.tap {|hash| attributes.each {|k, v| hash[transform(k)] = v}}
|
749
|
-
# This doesn't work yet, but at least it wont downcase hash keys.
|
750
|
-
#attributes = Hash.new.tap {|hash| attributes.each {|k, v| hash[transform(k)] = v}}
|
751
|
-
end
|
752
|
-
set_cursor cursor.receive_start_element(tag, attributes)
|
753
|
-
end
|
754
|
-
|
755
|
-
# Add attribute to existing element.
|
756
|
-
def _attribute(name, value, *args)
|
757
|
-
#puts "Receiving attribute '#{name}' with value '#{value}'"
|
758
|
-
name = transform name
|
759
|
-
cursor.receive_attribute(name, value)
|
760
|
-
end
|
761
|
-
|
762
|
-
# Add 'content' attribute to existing element.
|
763
|
-
def _text(value, *args)
|
764
|
-
#puts "Receiving text '#{value}'"
|
765
|
-
#puts RUBY_VERSION_NUM
|
766
|
-
if RUBY_VERSION_NUM > 1.8 && value.is_a?(String)
|
767
|
-
#puts "Forcing utf-8"
|
768
|
-
value.force_encoding('UTF-8')
|
769
|
-
end
|
770
|
-
# I think the reason this was here is no longer relevant, so I'm disabeling.
|
771
|
-
return unless value[/[^\s]/]
|
772
|
-
cursor.receive_attribute(TEXT_LABEL, value)
|
773
|
-
end
|
774
|
-
|
775
|
-
# Close out an existing element.
|
776
|
-
def _end_element(tag, *args)
|
777
|
-
tag = transform tag
|
778
|
-
#puts "Receiving end_element '#{tag}'"
|
779
|
-
cursor.receive_end_element(tag) and dump_cursor
|
780
|
-
end
|
781
|
-
|
782
|
-
def _doctype(*args)
|
783
|
-
(args = args[0].gsub(/"/, '').split) if args.size ==1
|
784
|
-
_start_element('doctype', :value=>args)
|
785
|
-
_end_element('doctype')
|
786
|
-
end
|
787
|
-
|
788
|
-
end # Handler
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
##### SAX PARSER BACKEND HANDLERS #####
|
793
|
-
|
794
|
-
PARSERS[:libxml] = {:file=>'libxml-ruby', :proc => proc do
|
795
|
-
require 'libxml'
|
796
|
-
class LibxmlHandler
|
797
|
-
include LibXML
|
798
|
-
include XML::SaxParser::Callbacks
|
799
|
-
include Handler
|
800
|
-
|
801
|
-
def run_parser(io)
|
802
|
-
parser = case
|
803
|
-
when (io.is_a?(File) or io.is_a?(StringIO)); XML::SaxParser.io(io)
|
804
|
-
when io[/^</]; XML::SaxParser.io(StringIO.new(io))
|
805
|
-
else XML::SaxParser.io(File.new(io))
|
806
|
-
end
|
807
|
-
parser.callbacks = self
|
808
|
-
parser.parse
|
809
|
-
end
|
810
|
-
|
811
|
-
# def on_start_element_ns(name, attributes, prefix, uri, namespaces)
|
812
|
-
# attributes.merge!(:prefix=>prefix, :uri=>uri, :xmlns=>namespaces)
|
813
|
-
# _start_element(name, attributes)
|
814
|
-
# end
|
815
|
-
|
816
|
-
alias_method :on_start_element, :_start_element
|
817
|
-
alias_method :on_end_element, :_end_element
|
818
|
-
alias_method :on_characters, :_text
|
819
|
-
alias_method :on_internal_subset, :_doctype
|
820
|
-
end # LibxmlSax
|
821
|
-
end}
|
822
|
-
|
823
|
-
PARSERS[:nokogiri] = {:file=>'nokogiri', :proc => proc do
|
824
|
-
require 'nokogiri'
|
825
|
-
class NokogiriHandler < Nokogiri::XML::SAX::Document
|
826
|
-
include Handler
|
827
|
-
|
828
|
-
def run_parser(io)
|
829
|
-
parser = Nokogiri::XML::SAX::Parser.new(self)
|
830
|
-
parser.parse(case
|
831
|
-
when (io.is_a?(File) or io.is_a?(StringIO)); io
|
832
|
-
when io[/^</]; StringIO.new(io)
|
833
|
-
else File.new(io)
|
834
|
-
end)
|
835
|
-
end
|
836
|
-
|
837
|
-
alias_method :start_element, :_start_element
|
838
|
-
alias_method :end_element, :_end_element
|
839
|
-
alias_method :characters, :_text
|
840
|
-
end # NokogiriSax
|
841
|
-
end}
|
842
|
-
|
843
|
-
PARSERS[:ox] = {:file=>'ox', :proc => proc do
|
844
|
-
require 'ox'
|
845
|
-
class OxHandler < ::Ox::Sax
|
846
|
-
include Handler
|
847
|
-
|
848
|
-
def run_parser(io)
|
849
|
-
options={:convert_special=>true}
|
850
|
-
case
|
851
|
-
when (io.is_a?(File) or io.is_a?(StringIO)); Ox.sax_parse self, io, options
|
852
|
-
when io.to_s[/^</]; StringIO.open(io){|f| Ox.sax_parse self, f, options}
|
853
|
-
else File.open(io){|f| Ox.sax_parse self, f, options}
|
854
|
-
end
|
855
|
-
end
|
856
|
-
|
857
|
-
alias_method :start_element, :_start_element
|
858
|
-
alias_method :end_element, :_end_element
|
859
|
-
alias_method :attr, :_attribute
|
860
|
-
alias_method :text, :_text
|
861
|
-
alias_method :doctype, :_doctype
|
862
|
-
end # OxFmpSax
|
863
|
-
end}
|
864
|
-
|
865
|
-
PARSERS[:rexml] = {:file=>'rexml/document', :proc => proc do
|
866
|
-
require 'rexml/document'
|
867
|
-
require 'rexml/streamlistener'
|
868
|
-
class RexmlHandler
|
869
|
-
# Both of these are needed to use rexml streaming parser,
|
870
|
-
# but don't put them here... put them at the _top.
|
871
|
-
#require 'rexml/streamlistener'
|
872
|
-
#require 'rexml/document'
|
873
|
-
include REXML::StreamListener
|
874
|
-
include Handler
|
875
|
-
|
876
|
-
def run_parser(io)
|
877
|
-
parser = REXML::Document
|
878
|
-
case
|
879
|
-
when (io.is_a?(File) or io.is_a?(StringIO)); parser.parse_stream(io, self)
|
880
|
-
when io.to_s[/^</]; StringIO.open(io){|f| parser.parse_stream(f, self)}
|
881
|
-
else File.open(io){|f| parser.parse_stream(f, self)}
|
882
|
-
end
|
883
|
-
end
|
884
|
-
|
885
|
-
alias_method :tag_start, :_start_element
|
886
|
-
alias_method :tag_end, :_end_element
|
887
|
-
alias_method :text, :_text
|
888
|
-
alias_method :doctype, :_doctype
|
889
|
-
end # RexmlStream
|
890
|
-
end}
|
891
|
-
|
892
|
-
|
893
|
-
end # SaxParser
|
726
|
+
def set_cursor(args) # cursor_object
|
727
|
+
if args.is_a? Cursor
|
728
|
+
stack.push(args)
|
729
|
+
#@stack_debug.push(args.dup.tap(){|c| c.handler = c.handler.object_id; c.parent = c.parent.tag})
|
730
|
+
end
|
731
|
+
cursor
|
732
|
+
end
|
733
|
+
|
734
|
+
def dump_cursor
|
735
|
+
stack.pop
|
736
|
+
end
|
737
|
+
|
738
|
+
def top
|
739
|
+
stack[0]
|
740
|
+
end
|
741
|
+
|
742
|
+
def transform(name)
|
743
|
+
return name unless TAG_TRANSLATION.is_a?(Proc)
|
744
|
+
TAG_TRANSLATION.call(name.to_s)
|
745
|
+
end
|
746
|
+
|
747
|
+
# Add a node to an existing element.
|
748
|
+
def _start_element(tag, attributes=nil, *args)
|
749
|
+
#puts ["_START_ELEMENT", tag, attributes, args].to_yaml # if tag.to_s.downcase=='fmrestulset'
|
750
|
+
tag = transform tag
|
751
|
+
if attributes
|
752
|
+
# This crazy thing transforms attribute keys to underscore (or whatever).
|
753
|
+
#attributes = default_class[*attributes.collect{|k,v| [transform(k),v] }.flatten]
|
754
|
+
# This works but downcases all attribute names - not good.
|
755
|
+
attributes = DEFAULT_CLASS.new.tap {|hash| attributes.each {|k, v| hash[transform(k)] = v}}
|
756
|
+
# This doesn't work yet, but at least it wont downcase hash keys.
|
757
|
+
#attributes = Hash.new.tap {|hash| attributes.each {|k, v| hash[transform(k)] = v}}
|
758
|
+
end
|
759
|
+
set_cursor cursor.receive_start_element(tag, attributes)
|
760
|
+
end
|
761
|
+
|
762
|
+
# Add attribute to existing element.
|
763
|
+
def _attribute(name, value, *args)
|
764
|
+
#puts "Receiving attribute '#{name}' with value '#{value}'"
|
765
|
+
name = transform name
|
766
|
+
cursor.receive_attribute(name, value)
|
767
|
+
end
|
768
|
+
|
769
|
+
# Add 'content' attribute to existing element.
|
770
|
+
def _text(value, *args)
|
771
|
+
#puts "Receiving text '#{value}'"
|
772
|
+
#puts RUBY_VERSION_NUM
|
773
|
+
if RUBY_VERSION_NUM > 1.8 && value.is_a?(String)
|
774
|
+
#puts "Forcing utf-8"
|
775
|
+
value.force_encoding('UTF-8')
|
776
|
+
end
|
777
|
+
# I think the reason this was here is no longer relevant, so I'm disabeling.
|
778
|
+
return unless value[/[^\s]/]
|
779
|
+
cursor.receive_attribute(TEXT_LABEL, value)
|
780
|
+
end
|
781
|
+
|
782
|
+
# Close out an existing element.
|
783
|
+
def _end_element(tag, *args)
|
784
|
+
tag = transform tag
|
785
|
+
#puts "Receiving end_element '#{tag}'"
|
786
|
+
cursor.receive_end_element(tag) and dump_cursor
|
787
|
+
end
|
788
|
+
|
789
|
+
def _doctype(*args)
|
790
|
+
(args = args[0].gsub(/"/, '').split) if args.size ==1
|
791
|
+
_start_element('doctype', :value=>args)
|
792
|
+
_end_element('doctype')
|
793
|
+
end
|
794
|
+
|
795
|
+
end # Handler
|
796
|
+
|
797
|
+
|
798
|
+
|
799
|
+
##### SAX PARSER BACKEND HANDLERS #####
|
800
|
+
|
801
|
+
PARSERS[:libxml] = {:file=>'libxml-ruby', :proc => proc do
|
802
|
+
require 'libxml'
|
803
|
+
class LibxmlHandler
|
804
|
+
include LibXML
|
805
|
+
include XML::SaxParser::Callbacks
|
806
|
+
include Handler
|
807
|
+
|
808
|
+
def run_parser(io)
|
809
|
+
parser = case
|
810
|
+
when (io.is_a?(File) || io.is_a?(StringIO))
|
811
|
+
XML::SaxParser.io(io)
|
812
|
+
when io[/^</]
|
813
|
+
XML::SaxParser.io(StringIO.new(io))
|
814
|
+
else
|
815
|
+
XML::SaxParser.io(File.new(io))
|
816
|
+
end
|
817
|
+
parser.callbacks = self
|
818
|
+
parser.parse
|
819
|
+
end
|
820
|
+
|
821
|
+
# def on_start_element_ns(name, attributes, prefix, uri, namespaces)
|
822
|
+
# attributes.merge!(:prefix=>prefix, :uri=>uri, :xmlns=>namespaces)
|
823
|
+
# _start_element(name, attributes)
|
824
|
+
# end
|
825
|
+
|
826
|
+
alias_method :on_start_element, :_start_element
|
827
|
+
alias_method :on_end_element, :_end_element
|
828
|
+
alias_method :on_characters, :_text
|
829
|
+
alias_method :on_internal_subset, :_doctype
|
830
|
+
end # LibxmlSax
|
831
|
+
end}
|
832
|
+
|
833
|
+
PARSERS[:nokogiri] = {:file=>'nokogiri', :proc => proc do
|
834
|
+
require 'nokogiri'
|
835
|
+
class NokogiriHandler < Nokogiri::XML::SAX::Document
|
836
|
+
include Handler
|
837
|
+
|
838
|
+
def run_parser(io)
|
839
|
+
parser = Nokogiri::XML::SAX::Parser.new(self)
|
840
|
+
parser.parse case
|
841
|
+
when (io.is_a?(File) || io.is_a?(StringIO))
|
842
|
+
io
|
843
|
+
when io[/^</]
|
844
|
+
StringIO.new(io)
|
845
|
+
else
|
846
|
+
File.new(io)
|
847
|
+
end
|
848
|
+
end
|
849
|
+
|
850
|
+
alias_method :start_element, :_start_element
|
851
|
+
alias_method :end_element, :_end_element
|
852
|
+
alias_method :characters, :_text
|
853
|
+
end # NokogiriSax
|
854
|
+
end}
|
855
|
+
|
856
|
+
PARSERS[:ox] = {:file=>'ox', :proc => proc do
|
857
|
+
require 'ox'
|
858
|
+
class OxHandler < ::Ox::Sax
|
859
|
+
include Handler
|
860
|
+
|
861
|
+
def run_parser(io)
|
862
|
+
options={:convert_special=>true}
|
863
|
+
case
|
864
|
+
when (io.is_a?(File) or io.is_a?(StringIO)); Ox.sax_parse self, io, options
|
865
|
+
when io.to_s[/^</]; StringIO.open(io){|f| Ox.sax_parse self, f, options}
|
866
|
+
else File.open(io){|f| Ox.sax_parse self, f, options}
|
867
|
+
end
|
868
|
+
end
|
869
|
+
|
870
|
+
alias_method :start_element, :_start_element
|
871
|
+
alias_method :end_element, :_end_element
|
872
|
+
alias_method :attr, :_attribute
|
873
|
+
alias_method :text, :_text
|
874
|
+
alias_method :doctype, :_doctype
|
875
|
+
end # OxFmpSax
|
876
|
+
end}
|
877
|
+
|
878
|
+
PARSERS[:rexml] = {:file=>'rexml/document', :proc => proc do
|
879
|
+
require 'rexml/document'
|
880
|
+
require 'rexml/streamlistener'
|
881
|
+
class RexmlHandler
|
882
|
+
# Both of these are needed to use rexml streaming parser,
|
883
|
+
# but don't put them here... put them at the _top.
|
884
|
+
#require 'rexml/streamlistener'
|
885
|
+
#require 'rexml/document'
|
886
|
+
include REXML::StreamListener
|
887
|
+
include Handler
|
888
|
+
|
889
|
+
def run_parser(io)
|
890
|
+
parser = REXML::Document
|
891
|
+
case
|
892
|
+
when (io.is_a?(File) or io.is_a?(StringIO)); parser.parse_stream(io, self)
|
893
|
+
when io.to_s[/^</]; StringIO.open(io){|f| parser.parse_stream(f, self)}
|
894
|
+
else File.open(io){|f| parser.parse_stream(f, self)}
|
895
|
+
end
|
896
|
+
end
|
897
|
+
|
898
|
+
alias_method :tag_start, :_start_element
|
899
|
+
alias_method :tag_end, :_end_element
|
900
|
+
alias_method :text, :_text
|
901
|
+
alias_method :doctype, :_doctype
|
902
|
+
end # RexmlStream
|
903
|
+
end}
|
904
|
+
|
905
|
+
end # SaxParser
|
894
906
|
end # Rfm
|
895
907
|
|
896
908
|
|
@@ -900,236 +912,236 @@ end # Rfm
|
|
900
912
|
|
901
913
|
class Object
|
902
914
|
|
903
|
-
|
904
|
-
|
905
|
-
|
906
|
-
|
907
|
-
|
908
|
-
|
909
|
-
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
|
927
|
-
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
934
|
-
|
935
|
-
|
936
|
-
|
937
|
-
|
938
|
-
|
939
|
-
|
940
|
-
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
|
948
|
-
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
|
959
|
-
|
960
|
-
|
961
|
-
|
962
|
-
|
963
|
-
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
|
968
|
-
|
969
|
-
|
970
|
-
|
971
|
-
|
972
|
-
|
973
|
-
|
974
|
-
|
975
|
-
|
976
|
-
|
977
|
-
|
978
|
-
|
979
|
-
|
980
|
-
|
981
|
-
|
982
|
-
|
983
|
-
|
984
|
-
|
985
|
-
|
986
|
-
|
987
|
-
|
988
|
-
|
989
|
-
|
990
|
-
|
991
|
-
|
992
|
-
|
993
|
-
|
994
|
-
|
995
|
-
|
996
|
-
|
997
|
-
|
998
|
-
|
999
|
-
|
1000
|
-
|
1001
|
-
|
1002
|
-
|
915
|
+
# Master method to attach any object to this object.
|
916
|
+
def _attach_object!(obj, *args) # name/label, collision-delimiter, attachment-prefs, type, *options: <options>
|
917
|
+
#puts ["\nATTACH_OBJECT._attach_object", "self.class: #{self.class}", "obj.class: #{obj.class}", "obj.to_s: #{obj.to_s}", "args: #{args}"]
|
918
|
+
options = ATTACH_OBJECT_DEFAULT_OPTIONS.merge(args.last.is_a?(Hash) ? args.pop : {}){|key, old, new| new || old}
|
919
|
+
# name = (args[0] || options[:name])
|
920
|
+
# delimiter = (args[1] || options[:delimiter])
|
921
|
+
prefs = (args[2] || options[:prefs])
|
922
|
+
# type = (args[3] || options[:type])
|
923
|
+
return if (prefs=='none' || prefs=='cursor') #['none', 'cursor'].include? prefs ... not sure which is faster.
|
924
|
+
self._merge_object!(
|
925
|
+
obj,
|
926
|
+
args[0] || options[:name] || 'unknown_name',
|
927
|
+
args[1] || options[:delimiter],
|
928
|
+
prefs,
|
929
|
+
args[3] || options[:type],
|
930
|
+
options
|
931
|
+
)
|
932
|
+
|
933
|
+
# case
|
934
|
+
# when prefs=='none' || prefs=='cursor'; nil
|
935
|
+
# when name
|
936
|
+
# self._merge_object!(obj, name, delimiter, prefs, type, options)
|
937
|
+
# else
|
938
|
+
# self._merge_object!(obj, 'unknown_name', delimiter, prefs, type, options)
|
939
|
+
# end
|
940
|
+
#puts ["\nATTACH_OBJECT RESULT", self.to_yaml]
|
941
|
+
#puts ["\nATTACH_OBJECT RESULT PORTALS", (self.portals.to_yaml rescue 'no portals')]
|
942
|
+
end
|
943
|
+
|
944
|
+
# Master method to merge any object with this object
|
945
|
+
def _merge_object!(obj, name, delimiter, prefs, type, options={})
|
946
|
+
#puts ["\n-----OBJECT._merge_object", self.class, (obj.to_s rescue obj.class), name, delimiter, prefs, type.capitalize, options].join(', ')
|
947
|
+
if prefs=='private'
|
948
|
+
_merge_instance!(obj, name, delimiter, prefs, type, options)
|
949
|
+
else
|
950
|
+
_merge_shared!(obj, name, delimiter, prefs, type, options)
|
951
|
+
end
|
952
|
+
end
|
953
|
+
|
954
|
+
# Merge a named object with the shared instance variable of self.
|
955
|
+
def _merge_shared!(obj, name, delimiter, prefs, type, options={})
|
956
|
+
shared_var = instance_variable_get("@#{options[:shared_variable_name]}") || instance_variable_set("@#{options[:shared_variable_name]}", options[:default_class].new)
|
957
|
+
#puts "\n-----OBJECT._merge_shared: self '#{self.class}' obj '#{obj.class}' name '#{name}' delimiter '#{delimiter}' type '#{type}' shared_var '#{options[:shared_variable_name]} - #{shared_var.class}'"
|
958
|
+
# TODO: Figure this part out:
|
959
|
+
# The resetting of shared_variable_name to 'attributes' was to fix Asset.field_controls (it was not able to find the valuelive name).
|
960
|
+
# I think there might be a level of hierarchy that is without a proper cursor model, when using shared variables & object delimiters.
|
961
|
+
shared_var._merge_object!(obj, name, delimiter, nil, type, options.merge(:shared_variable_name=>ATTACH_OBJECT_DEFAULT_OPTIONS[:shared_variable_name]))
|
962
|
+
end
|
963
|
+
|
964
|
+
# Merge a named object with the specified instance variable of self.
|
965
|
+
def _merge_instance!(obj, name, delimiter, prefs, type, options={})
|
966
|
+
#puts ["\nOBJECT._merge_instance!", "self.class: #{self.class}", "obj.class: #{obj.class}", "name: #{name}", "delimiter: #{delimiter}", "prefs: #{prefs}", "type: #{type}", "options.keys: #{options.keys}", '_end_merge_instance!'] #.join(', ')
|
967
|
+
rslt = if instance_variable_get("@#{name}") || delimiter
|
968
|
+
if delimiter
|
969
|
+
delimit_name = obj._get_attribute(delimiter, options[:shared_variable_name]).to_s.downcase
|
970
|
+
#puts ["\n_setting_with_delimiter", delimit_name]
|
971
|
+
#instance_variable_set("@#{name}", instance_variable_get("@#{name}") || options[:default_class].new)[delimit_name]=obj
|
972
|
+
# This line is more efficient than the above line.
|
973
|
+
instance_variable_set("@#{name}", options[:default_class].new) unless instance_variable_get("@#{name}")
|
974
|
+
|
975
|
+
# This line was active in 3.0.9.pre01, but it was inserting each portal array as an element in the array,
|
976
|
+
# after all the Rfm::Record instances had been added. This was causing an potential infinite recursion situation.
|
977
|
+
# I don't think this line was supposed to be here - was probably an older piece of code.
|
978
|
+
#instance_variable_get("@#{name}")[delimit_name]=obj
|
979
|
+
|
980
|
+
#instance_variable_get("@#{name}")._merge_object!(obj, delimit_name, nil, nil, nil)
|
981
|
+
# Trying to handle multiple portals with same table-occurance on same layout.
|
982
|
+
# In parsing terms, this is trying to handle multiple elements who's delimiter field contains the SAME delimiter data.
|
983
|
+
instance_variable_get("@#{name}")._merge_delimited_object!(obj, delimit_name)
|
984
|
+
else
|
985
|
+
#puts ["\_setting_existing_instance_var", name]
|
986
|
+
if name == options[:text_label]
|
987
|
+
instance_variable_get("@#{name}") << obj.to_s
|
988
|
+
else
|
989
|
+
instance_variable_set("@#{name}", [instance_variable_get("@#{name}")].flatten << obj)
|
990
|
+
end
|
991
|
+
end
|
992
|
+
else
|
993
|
+
#puts ["\n_setting_new_instance_var", name]
|
994
|
+
instance_variable_set("@#{name}", obj)
|
995
|
+
end
|
996
|
+
|
997
|
+
# NEW
|
998
|
+
_create_accessor(name) if (options[:create_accessors] & ['all','private']).any?
|
999
|
+
|
1000
|
+
rslt
|
1001
|
+
end
|
1002
|
+
|
1003
|
+
def _merge_delimited_object!(obj, delimit_name)
|
1004
|
+
#puts "MERGING DELIMITED OBJECT self #{self.class} obj #{obj.class} delimit_name #{delimit_name}"
|
1005
|
+
|
1006
|
+
case
|
1007
|
+
when self[delimit_name].nil?; self[delimit_name] = obj
|
1008
|
+
when self[delimit_name].is_a?(Hash); self[delimit_name].merge!(obj)
|
1009
|
+
when self[delimit_name].is_a?(Array); self[delimit_name] << obj
|
1010
|
+
else self[delimit_name] = [self[delimit_name], obj]
|
1011
|
+
end
|
1012
|
+
end
|
1013
|
+
|
1014
|
+
# Get an instance variable, a member of a shared instance variable, or a hash value of self.
|
1003
1015
|
def _get_attribute(name, shared_var_name=nil, options={})
|
1004
|
-
|
1005
|
-
|
1006
|
-
|
1007
|
-
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1016
|
+
return unless name
|
1017
|
+
#puts ["\n\n", self.to_yaml]
|
1018
|
+
#puts ["OBJECT_get_attribute", self.class, self.instance_variables, name, shared_var_name, options].join(', ')
|
1019
|
+
(shared_var_name = options[:shared_variable_name]) unless shared_var_name
|
1020
|
+
|
1021
|
+
rslt = case
|
1022
|
+
when self.is_a?(Hash) && self[name]; self[name]
|
1023
|
+
when ((var= instance_variable_get("@#{shared_var_name}")) && var[name]); var[name]
|
1024
|
+
else instance_variable_get("@#{name}")
|
1025
|
+
end
|
1026
|
+
|
1027
|
+
#puts "OBJECT#_get_attribute: name '#{name}' shared_var_name '#{shared_var_name}' options '#{options}' rslt '#{rslt}'"
|
1028
|
+
rslt
|
1017
1029
|
end
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
1031
|
-
|
1032
|
-
|
1033
|
-
|
1034
|
-
|
1030
|
+
|
1031
|
+
# # We don't know which attributes are shared, so this isn't really accurate per the options.
|
1032
|
+
# # But this could be useful for mass-attachment of a set of attributes (to increase performance in some situations).
|
1033
|
+
# def _create_accessors options=[]
|
1034
|
+
# options=[options].flatten.compact
|
1035
|
+
# #puts ['CREATE_ACCESSORS', self.class, options, ""]
|
1036
|
+
# return false if (options & ['none']).any?
|
1037
|
+
# if (options & ['all', 'private']).any?
|
1038
|
+
# meta = (class << self; self; end)
|
1039
|
+
# meta.send(:attr_reader, *instance_variables.collect{|v| v.to_s[1,99].to_sym})
|
1040
|
+
# end
|
1041
|
+
# if (options & ['all', 'shared']).any?
|
1042
|
+
# instance_variables.collect{|v| instance_variable_get(v)._create_accessors('hash')}
|
1043
|
+
# end
|
1044
|
+
# return true
|
1045
|
+
# end
|
1046
|
+
|
1035
1047
|
# NEW
|
1036
1048
|
def _create_accessor(name)
|
1037
|
-
|
1038
|
-
|
1039
|
-
|
1040
|
-
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
1045
|
-
|
1046
|
-
|
1047
|
-
|
1048
|
-
|
1049
|
-
|
1050
|
-
|
1051
|
-
|
1052
|
-
|
1053
|
-
|
1054
|
-
|
1055
|
-
|
1056
|
-
|
1057
|
-
|
1049
|
+
#puts "OBJECT._create_accessor '#{name}' for Object '#{self.class}'"
|
1050
|
+
meta = (class << self; self; end)
|
1051
|
+
meta.send(:attr_reader, name.to_sym)
|
1052
|
+
end
|
1053
|
+
|
1054
|
+
# Attach hash as individual instance variables to self.
|
1055
|
+
# This is for manually attaching a hash of attributes to the current object.
|
1056
|
+
# Pass in translation procs to alter the keys or values.
|
1057
|
+
def _attach_as_instance_variables(hash, options={})
|
1058
|
+
#hash.each{|k,v| instance_variable_set("@#{k}", v)} if hash.is_a? Hash
|
1059
|
+
key_translator = options[:key_translator]
|
1060
|
+
value_translator = options[:value_translator]
|
1061
|
+
#puts ["ATTACH_AS_INSTANCE_VARIABLES", key_translator, value_translator].join(', ')
|
1062
|
+
if hash.is_a? Hash
|
1063
|
+
hash.each do |k,v|
|
1064
|
+
(k = key_translator.call(k)) if key_translator
|
1065
|
+
(v = value_translator.call(k, v)) if value_translator
|
1066
|
+
instance_variable_set("@#{k}", v)
|
1067
|
+
end
|
1068
|
+
end
|
1069
|
+
end
|
1058
1070
|
|
1059
1071
|
end # Object
|
1060
1072
|
|
1061
1073
|
class Array
|
1062
|
-
|
1063
|
-
|
1064
|
-
|
1065
|
-
|
1066
|
-
|
1067
|
-
|
1068
|
-
|
1069
|
-
|
1074
|
+
def _merge_object!(obj, name, delimiter, prefs, type, options={})
|
1075
|
+
#puts ["\n+++++ARRAY._merge_object", self.class, (obj.to_s rescue obj.class), name, delimiter, prefs, type, options].join(', ')
|
1076
|
+
case
|
1077
|
+
when prefs=='shared' || type == 'attribute' && prefs.to_s != 'private' ; _merge_shared!(obj, name, delimiter, prefs, type, options)
|
1078
|
+
when prefs=='private'; _merge_instance!(obj, name, delimiter, prefs, type, options)
|
1079
|
+
else self << obj
|
1080
|
+
end
|
1081
|
+
end
|
1070
1082
|
end # Array
|
1071
1083
|
|
1072
1084
|
class Hash
|
1073
|
-
|
1074
|
-
|
1075
|
-
|
1076
|
-
|
1077
|
-
|
1078
|
-
|
1079
|
-
|
1080
|
-
|
1081
|
-
|
1082
|
-
|
1083
|
-
|
1084
|
-
|
1085
|
-
|
1086
|
-
|
1087
|
-
|
1088
|
-
|
1089
|
-
|
1090
|
-
|
1091
|
-
|
1092
|
-
|
1093
|
-
|
1094
|
-
|
1095
|
-
|
1096
|
-
|
1097
|
-
|
1098
|
-
|
1099
|
-
|
1100
|
-
|
1101
|
-
|
1102
|
-
|
1103
|
-
|
1104
|
-
|
1105
|
-
|
1106
|
-
|
1107
|
-
|
1108
|
-
|
1109
|
-
|
1110
|
-
|
1111
|
-
|
1112
|
-
|
1113
|
-
|
1114
|
-
|
1115
|
-
|
1116
|
-
|
1117
|
-
|
1118
|
-
|
1119
|
-
|
1120
|
-
|
1121
|
-
|
1122
|
-
|
1123
|
-
|
1124
|
-
|
1125
|
-
|
1126
|
-
|
1127
|
-
|
1128
|
-
|
1129
|
-
|
1130
|
-
|
1131
|
-
|
1132
|
-
|
1085
|
+
|
1086
|
+
def _merge_object!(obj, name, delimiter, prefs, type, options={})
|
1087
|
+
#puts ["\n*****HASH._merge_object", "type: #{type}", "name: #{name}", "self.class: #{self.class}", "new_obj: #{(obj.to_s rescue obj.class)}", "delimiter: #{delimiter}", "prefs: #{prefs}", "options: #{options}"]
|
1088
|
+
output = case
|
1089
|
+
when prefs=='shared'
|
1090
|
+
_merge_shared!(obj, name, delimiter, prefs, type, options)
|
1091
|
+
when prefs=='private'
|
1092
|
+
_merge_instance!(obj, name, delimiter, prefs, type, options)
|
1093
|
+
when (self[name] || delimiter)
|
1094
|
+
rslt = if delimiter
|
1095
|
+
delimit_name = obj._get_attribute(delimiter, options[:shared_variable_name]).to_s.downcase
|
1096
|
+
#puts "MERGING delimited object with hash: self '#{self.class}' obj '#{obj.class}' name '#{name}' delim '#{delimiter}' delim_name '#{delimit_name}' options '#{options}'"
|
1097
|
+
self[name] ||= options[:default_class].new
|
1098
|
+
|
1099
|
+
#self[name][delimit_name]=obj
|
1100
|
+
# This is supposed to handle multiple elements who's delimiter value is the SAME.
|
1101
|
+
self[name]._merge_delimited_object!(obj, delimit_name)
|
1102
|
+
else
|
1103
|
+
if name == options[:text_label]
|
1104
|
+
self[name] << obj.to_s
|
1105
|
+
else
|
1106
|
+
self[name] = [self[name]].flatten
|
1107
|
+
self[name] << obj
|
1108
|
+
end
|
1109
|
+
end
|
1110
|
+
_create_accessor(name) if (options[:create_accessors].to_a & ['all','shared','hash']).any?
|
1111
|
+
|
1112
|
+
rslt
|
1113
|
+
else
|
1114
|
+
rslt = self[name] = obj
|
1115
|
+
_create_accessor(name) if (options[:create_accessors] & ['all','shared','hash']).any?
|
1116
|
+
rslt
|
1117
|
+
end
|
1118
|
+
#puts ["\n*****HASH._merge_object! RESULT", self.to_yaml]
|
1119
|
+
#puts ["\n*****HASH._merge_object! RESULT PORTALS", (self.portals.to_yaml rescue 'no portals')]
|
1120
|
+
output
|
1121
|
+
end
|
1122
|
+
|
1123
|
+
# def _create_accessors options=[]
|
1124
|
+
# #puts ['CREATE_ACCESSORS_for_HASH', self.class, options]
|
1125
|
+
# options=[options].flatten.compact
|
1126
|
+
# super and
|
1127
|
+
# if (options & ['all', 'hash']).any?
|
1128
|
+
# meta = (class << self; self; end)
|
1129
|
+
# keys.each do |k|
|
1130
|
+
# meta.send(:define_method, k) do
|
1131
|
+
# self[k]
|
1132
|
+
# end
|
1133
|
+
# end
|
1134
|
+
# end
|
1135
|
+
# end
|
1136
|
+
|
1137
|
+
def _create_accessor(name)
|
1138
|
+
#puts "HASH._create_accessor '#{name}' for Hash '#{self.class}'"
|
1139
|
+
meta = (class << self; self; end)
|
1140
|
+
meta.send(:define_method, name) do
|
1141
|
+
self[name]
|
1142
|
+
end
|
1143
|
+
end
|
1144
|
+
|
1133
1145
|
end # Hash
|
1134
1146
|
|
1135
1147
|
|
@@ -1194,11 +1206,11 @@ end # Hash
|
|
1194
1206
|
# TODO: compact is not working for fmpxmllayout-error. Consider rewrite of 'compact' method, or allow compact to work on end_element with no matching tag.
|
1195
1207
|
# mabe: Add ability to put a regex in the as_name parameter, that will operate on the tag/label/name.
|
1196
1208
|
# TODO: Optimize:
|
1197
|
-
#
|
1198
|
-
#
|
1199
|
-
#
|
1200
|
-
#
|
1201
|
-
#
|
1209
|
+
# Use variables, not methods.
|
1210
|
+
# Use string interpolation not concatenation.
|
1211
|
+
# Use destructive! operations (carefully). Really?
|
1212
|
+
# Get this book: http://my.safaribooksonline.com/9780321540034?portal=oreilly
|
1213
|
+
# See this page: http://www.igvita.com/2008/07/08/6-optimization-tips-for-ruby-mri/
|
1202
1214
|
# done: Consider making default attribute-attachment shared, instead of instance.
|
1203
1215
|
# done: Find a way to get SaxParser defaults into core class patches.
|
1204
1216
|
# done: Check resultset portal_meta in Splash Asset model for field-definitions coming out correct according to sax template.
|
@@ -1206,7 +1218,7 @@ end # Hash
|
|
1206
1218
|
# done: Scan thru Rfm classes and use @instance variables instead of their accessors, so sax-parser does less work.
|
1207
1219
|
# done: Change 'instance' option to 'private'. Change 'shared' to <whatever>.
|
1208
1220
|
# done: Since unattached elements won't callback, add their callback to an array of procs on the current cursor at the beginning
|
1209
|
-
#
|
1221
|
+
# of the non-attached tag.
|
1210
1222
|
# TODO: Handle check-for-errors in non-resultset loads.
|
1211
1223
|
# abrt: Figure out way to get doctype from nokogiri. Tried, may be practically impossible.
|
1212
1224
|
# TODO: Clean up sax code so that no 'rescue' is needed - if an error happens it should be a legit error outside of SaxParser.
|