ginjo-rfm 3.0.9 → 3.0.10
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.
- 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.
|