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.
@@ -9,193 +9,189 @@
9
9
  module Rfm
10
10
 
11
11
  module Factory
12
- # Acquired from Rfm::Base
13
- @models ||= []
14
-
15
- extend Config
16
- config :parent=>'Rfm::Config'
17
-
18
- class ServerFactory < Rfm::CaseInsensitiveHash
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
- options = Factory.get_config(*args)
22
- host = options[:strings].delete_at(0) || options[:host]
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
- # super(host).config(options) if (options)
27
- # super(host)
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
- # extend Config
36
- # config :parent=>'@server'
32
+ # extend Config
33
+ # config :parent=>'@server'
37
34
 
38
-
39
35
  def initialize(server)
40
- extend Config
41
- config :parent=>'@server'
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
- # was: (dbname, acnt=nil, pass=nil)
48
- options = get_config(*args)
49
- name = options[:strings].delete_at(0) || options[:database]
50
- #account_name = options[:strings].delete_at(0) || options[:account_name]
51
- #password = options[:strings].delete_at(0) || options[:password]
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
- # super(name).config({:account_name=>account_name, :password=>password}.merge(options)) if (account_name or password or options)
55
- # super(name)
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
- c = Connection.new('-dbnames', {}, {:grammar=>'FMPXMLRESULT'}, @server)
61
- c.parse('fmpxml_minimal.yml', {})['data'].each{|k,v| (self[k] = Rfm::Database.new(v['text'], @server)) if k.to_s != '' && v['text']}
62
- #r = c.parse('fmpxml_minimal.yml', {})
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
- self.values.collect{|v| v.name}
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
- # extend Config
79
- # config :parent=>'@database'
80
-
74
+ # extend Config
75
+ # config :parent=>'@database'
76
+
81
77
  def initialize(server, database)
82
- extend Config
83
- config :parent=>'@database'
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
- options = get_config(*args)
91
- name = options[:strings].delete_at(0) || options[:layout]
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
- # super(name).config({:layout=>name}.merge(options)) if options
95
- # super(name)
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
- c = Connection.new('-layoutnames', {"-db" => @database.name}, {:grammar=>'FMPXMLRESULT'}, @database)
101
- c.parse('fmpxml_minimal.yml', {})['data'].each{|k,v| (self[k] = Rfm::Layout.new(v['text'], @database)) if k.to_s != '' && v['text']}
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
- def names
108
- values.collect{|v| v.name}
109
- end
110
-
111
- # Acquired from Rfm::Base
112
- def modelize(filter = /.*/)
113
- all.values.each{|lay| lay.modelize if lay.name.match(filter)}
114
- models
115
- end
116
-
117
- # Acquired from Rfm::Base
118
- def models
119
- rslt = {}
120
- each do |k,lay|
121
- layout_models = lay.models
122
- rslt[k] = layout_models if (!layout_models.nil? && !layout_models.empty?)
123
- end
124
- rslt
125
- end
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
- # extend Config
134
- # config :parent=>'@database'
135
-
129
+ # extend Config
130
+ # config :parent=>'@database'
131
+
136
132
  def initialize(server, database)
137
- extend Config
138
- config :parent=>'@database'
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
- c = Connection.new('-scriptnames', {"-db" => @database.name}, {:grammar=>'FMPXMLRESULT'}, @database)
151
- c.parse('fmpxml_minimal.yml', {})['data'].each{|k,v| (self[k] = Rfm::Metadata::Script.new(v['text'], @database)) if k.to_s != '' && v['text']}
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
- def names
158
- values.collect{|v| v.name}
159
- end
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
- # Acquired from Rfm::Base
168
- attr_accessor :models
169
- # Shortcut to Factory.db().layouts.modelize()
170
- # If first parameter is regex, it is used for modelize filter.
171
- # Otherwise, parameters are passed to Factory.database
172
- def modelize(*args)
173
- regx = args[0].is_a?(Regexp) ? args.shift : /.*/
174
- db(*args).layouts.modelize(regx)
175
- end
176
-
177
- def servers
178
- @servers ||= ServerFactory.new
179
- end
180
-
181
- # Returns Rfm::Server instance, given config hash or array
182
- def server(*conf)
183
- Server.new(*conf)
184
- end
185
-
186
- # Returns Rfm::Db instance, given config hash or array
187
- def db(*conf)
188
- Database.new(*conf)
189
- end
190
-
191
- alias_method :database, :db
192
-
193
- # Returns Rfm::Layout instance, given config hash or array
194
- def layout(*conf)
195
- Layout.new(*conf)
196
- end
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
- # 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.
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
- # Both of these will still allow processing of attributes and subelements.
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: 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.
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
- 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
-
182
- end
183
-
184
- def initialize(_tag, _handler, _parent=nil, _initial_attributes=nil) #, caller_binding=nil)
185
- #def initialize(_model, _obj, _tag, _handler)
186
- @tag = _tag
187
- @handler = _handler
188
- @parent = _parent || self
189
- @initial_attributes = _initial_attributes
190
- @level = @parent.level.to_i + 1
191
- @local_model = (model_elements?(@tag, @parent.model) || DEFAULT_CLASS.new)
192
- @element_attachment_prefs = attachment_prefs(@parent.model, @local_model, 'element')
193
- #@attribute_attachment_prefs = attachment_prefs(@parent.model, @local_model, 'attribute')
194
-
195
- if @element_attachment_prefs.is_a? Array
196
- @new_element_callback = @element_attachment_prefs[1..-1]
197
- @element_attachment_prefs = @element_attachment_prefs[0]
198
- if @element_attachment_prefs.to_s == 'default'; @element_attachment_prefs = nil; end
199
- end
200
-
201
- #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}"]
202
-
203
- self
204
- end
205
-
206
-
207
- ##### SAX METHODS #####
208
-
209
- # Receive a single attribute (any named attribute or text)
210
- def receive_attribute(name, value)
211
- #puts ["\nRECEIVE_ATTR '#{name}'", "value: #{value}", "tag: #{@tag}", "object: #{object.class}", "model: #{model['name']}"]
212
- new_att = {name=>value} #.new.tap{|att| att[name]=value}
213
-
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
-
224
- new_cursor
225
- end # receive_start_element
226
-
227
- # Decides how to attach element & attributes associated with this cursor.
228
- def process_new_element(caller_binding=binding)
229
-
230
- #puts ["\nPROCESS_NEW_ELEMENT tag: #{@tag}", "@element_attachment_prefs: #{@element_attachment_prefs}", "@local_model: #{local_model}"]
231
-
232
- new_element = @new_element_callback ? get_callback(@new_element_callback, caller_binding) : nil
233
-
234
- case
235
-
236
- # when inital cursor, just set model & object.
237
- when @tag == '__TOP__';
238
- #puts "__TOP__"
239
- @model = @handler.template
240
- @object = @handler.initial_object
241
-
242
- when @element_attachment_prefs == 'none';
243
- #puts "__NONE__"
244
- @model = @parent.model #nil
245
- @object = @parent.object #nil
246
-
247
- if @initial_attributes && @initial_attributes.any? #&& @attribute_attachment_prefs != 'none'
248
- assign_attributes(@initial_attributes) #, @object, @model, @local_model)
249
- end
250
-
251
- when @element_attachment_prefs == 'cursor';
252
- #puts "__CURSOR__"
253
- @model = @local_model
254
- @object = new_element || DEFAULT_CLASS.allocate
255
-
256
- if @initial_attributes && @initial_attributes.any? #&& @attribute_attachment_prefs != 'none'
257
- assign_attributes(@initial_attributes) #, @object, @model, @local_model)
258
- end
259
-
260
- else
261
- #puts "__OTHER__"
262
- @model = @local_model
263
- @object = new_element || DEFAULT_CLASS.allocate
264
-
265
- if @initial_attributes && @initial_attributes.any? #&& @attribute_attachment_prefs != 'none'
266
- #puts "PROCESS_NEW_ELEMENT calling assign_attributes with ATTRIBUTES #{@initial_attributes}"
267
- assign_attributes(@initial_attributes) #, @object, @model, @local_model)
268
- end
269
-
270
- # If @local_model has a delimiter, defer attach_new_element until later.
271
- #puts "PROCESS_NEW_ELEMENT delimiter of @local_model #{delimiter?(@local_model)}"
272
- if !delimiter?(@local_model)
273
- #attach_new_object(@parent.object, @object, @tag, @parent.model, @local_model, 'element')
274
- #puts "PROCESS_NEW_ELEMENT calling attach_new_element with TAG #{@tag} and OBJECT #{@object}"
275
- attach_new_element(@tag, @object)
276
- end
277
- end
278
-
279
- self
280
- end
281
-
282
-
283
- def receive_end_element(_tag)
284
- #puts ["\nRECEIVE_END_ELEMENT '#{_tag}'", "tag: #{@tag}", "object: #{@object.class}", "model: #{@model['name']}", "local_model: #{@local_model['name']}"]
285
- #puts ["\nEND_ELEMENT_OBJECT", object.to_yaml]
286
- begin
287
-
288
- if _tag == @tag && (@model == @local_model)
289
- # Data cleaup
290
- compactor_settings = compact? || compact?(top.model)
291
- #(compactor_settings = compact?(top.model)) unless compactor_settings # prefer local settings, or use top settings.
292
- (clean_members {|v| clean_members(v){|v| clean_members(v)}}) if compactor_settings
293
- end
294
-
295
- if (delimiter = delimiter?(@local_model); delimiter && !['none','cursor'].include?(@element_attachment_prefs.to_s))
296
- #attach_new_object(@parent.object, @object, @tag, @parent.model, @local_model, 'element')
297
- #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}"
298
- attach_new_element(@tag, @object)
299
- end
300
-
301
- if _tag == @tag #&& (@model == @local_model)
302
- # End-element callbacks.
303
- #run_callback(_tag, self)
304
- callback = before_close?(@local_model)
305
- get_callback(callback, binding) if callback
306
- end
307
-
308
- if _tag == @tag
309
- # return true only if matching tags
310
- return true
311
- end
312
-
313
- # # return true only if matching tags
314
- # if _tag == @tag
315
- # return true
316
- # end
317
-
318
- return
319
- # rescue
320
- # Rfm.log.debug "Error: end_element tag '#{_tag}' failed: #{$!}"
321
- end
322
- end
323
-
324
- ### Parse callback instructions, compile & send callback method ###
325
- ### TODO: This is way too convoluted. Document it better, or refactor!!!
326
- # This method will send a method to an object, with parameters, and return a new object.
327
- # Input (first param): string, symbol, or array of strings
328
- # Returns: object
329
- # Default options:
330
- # :object=>object
331
- # :method=>'a method name string or symbol'
332
- # :params=>"params string to be eval'd in context of cursor"
333
- # Usage:
334
- # callback: send a method (or eval string) to an object with parameters, consisting of...
335
- # string: a string to be eval'd in context of current object.
336
- # or symbol: method to be called on current object.
337
- # or array: object, method, params.
338
- # object: <object or string>
339
- # method: <string or symbol>
340
- # params: <string>
341
- #
342
- # TODO-MAYBE: Change param order to (method, object, params),
343
- # might help confusion with param complexities.
344
- #
345
- def get_callback(callback, caller_binding=binding, defaults={})
346
- input = callback.is_a?(Array) ? callback.dup : callback
347
- #puts "\nGET_CALLBACK tag: #{tag}, callback: #{callback}"
348
- params = case
349
- when input.is_a?(String) || input.is_a?(Symbol)
350
- [nil, input]
351
- # when input.is_a?(Symbol)
352
- # [nil, input]
353
- when input.is_a?(Array)
354
- #puts ["\nCURSOR#get_callback is an array", input]
355
- case
356
- when input[0].is_a?(Symbol)
357
- [nil, input].flatten(1)
358
- when input[1].is_a?(String) && ( input.size > 2 || (remove_colon=(input[1][0,1]==":"); remove_colon) )
359
- code_or_method = input[1].dup
360
- code_or_method[0]='' if remove_colon
361
- code_or_method = code_or_method.to_sym
362
- output = [input[0], code_or_method, input[2..-1]].flatten(1)
363
- #puts ["\nCURSOR#get_callback converted input[1] to symbol", output]
364
- output
365
- else # when input is ['object', 'sym-or-str', 'param1',' param2', ...]
366
- input
367
- end
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
- obj_raw = params.shift
373
- #puts ["\nOBJECT_RAW:","class: #{obj_raw.class}", "object: #{obj_raw}"]
374
- obj = if obj_raw.is_a?(String); eval(obj_raw.to_s, caller_binding); else obj_raw; end
375
- if obj.nil? || obj == ''; obj = defaults[:object] || @object; end
376
- #puts ["\nOBJECT:","class: #{obj.class}", "object: #{obj}"]
377
-
378
- code = params.shift || defaults[:method]
379
- params.each_with_index{|str,i| if str.is_a?(String); params[i] = eval(str, caller_binding); end }
380
- params = defaults[:params] if params.size == 0
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
- # # Run before-close callback.
396
- # def run_callback(_tag, _cursor=self, _model=_cursor.local_model, _object=_cursor.object )
397
- # callback = before_close?(_model)
398
- # #puts ["\nRUN_CALLBACK", _tag, _cursor.tag, _object.class, callback, callback.class]
399
- # if callback.is_a? Symbol
400
- # _object.send callback, _cursor
401
- # elsif callback.is_a?(String)
402
- # _object.send :eval, callback
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
- # end
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
- ##### MERGE METHODS #####
410
-
411
- # Assign attributes to element.
412
- def assign_attributes(_attributes)
413
- if _attributes && !_attributes.empty?
414
-
415
- _attributes.each do |k,v|
416
- #attach_new_object(base_object, v, k, base_model, model_attributes?(k, new_model), 'attribute')}
417
- attr_model = model_attributes?(k, @local_model)
418
-
419
- label = label_or_tag(k, attr_model)
420
-
421
- prefs = [attachment_prefs(@model, attr_model, 'attribute')].flatten(1)[0]
422
-
423
- shared_var_name = shared_variable_name(prefs)
424
- (prefs = "shared") if shared_var_name
425
-
426
- # Use local create_accessors prefs first, then more general ones.
427
- create_accessors = accessor?(attr_model) || create_accessors?(@model)
428
- #(create_accessors = create_accessors?(@model)) unless create_accessors && create_accessors.any?
429
-
430
- #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}"]
431
- @object._attach_object!(v, label, delimiter?(attr_model), prefs, 'attribute', :default_class=>DEFAULT_CLASS, :shared_variable_name=>shared_var_name, :create_accessors=>create_accessors)
432
- end
433
-
434
- end
435
- end
436
-
437
- # def attach_new_object(base_object, new_object, name, base_model, new_model, type)
438
- # label = label_or_tag(name, new_model)
439
- #
440
- # # Was this, which works fine, but not as efficient:
441
- # # prefs = [attachment_prefs(base_model, new_model, type)].flatten(1)[0]
442
- # prefs = if type=='attribute'
443
- # [attachment_prefs(base_model, new_model, type)].flatten(1)[0]
444
- # else
445
- # @element_attachment_prefs
446
- # end
447
- #
448
- # shared_var_name = shared_variable_name(prefs)
449
- # (prefs = "shared") if shared_var_name
450
- #
451
- # # Use local create_accessors prefs first, then more general ones.
452
- # create_accessors = accessor?(new_model)
453
- # (create_accessors = create_accessors?(base_model)) unless create_accessors && create_accessors.any?
454
- #
455
- # # # This is NEW!
456
- # # translator = new_model['translator']
457
- # # if translator
458
- # # new_object = base_object.send translator, name, new_object
459
- # # end
460
- #
461
- #
462
- # #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}"]
463
- # 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)
464
- # #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]
465
- # # if type == 'attribute'
466
- # # 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}"]
467
- # # end
468
- # end
469
-
470
- def attach_new_element(name, new_object) #old params (base_object, new_object, name, base_model, new_model, type)
471
- label = label_or_tag(name, @local_model)
472
-
473
- # Was this, which works fine, but not as efficient:
474
- # prefs = [attachment_prefs(base_model, new_model, type)].flatten(1)[0]
475
- prefs = @element_attachment_prefs
476
-
477
- shared_var_name = shared_variable_name(prefs)
478
- (prefs = "shared") if shared_var_name
479
-
480
- # Use local create_accessors prefs first, then more general ones.
481
- create_accessors = accessor?(@local_model) || create_accessors?(@parent.model)
482
- #(create_accessors = create_accessors?(@parent.model)) unless create_accessors && create_accessors.any?
483
-
484
- # # This is NEW!
485
- # translator = new_model['translator']
486
- # if translator
487
- # new_object = base_object.send translator, name, new_object
488
- # end
489
-
490
-
491
- #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}"]
492
- @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)
493
- # if type == 'attribute'
494
- # 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}"]
495
- # end
496
- end
497
-
498
- def attachment_prefs(base_model, new_model, type)
499
- case type
500
- when 'element'; attach?(new_model) || attach_elements?(base_model) #|| attach?(top.model) || attach_elements?(top.model)
501
- when 'attribute'; attach?(new_model) || attach_attributes?(base_model) #|| attach?(top.model) || attach_attributes?(top.model)
502
- end
503
- end
504
-
505
- def shared_variable_name(prefs)
506
- rslt = nil
507
- if prefs.to_s[0,1] == "_"
508
- rslt = prefs.to_s[1..-1] #prefs.gsub(/^_/, '')
509
- end
510
- end
511
-
512
-
513
- ##### UTILITY #####
514
-
515
- def get_constant(klass)
516
- self.class.get_constant(klass)
517
- end
518
-
519
- # Methods for current _model
520
- def ivg(name, _object=@object); _object.instance_variable_get "@#{name}"; end
521
- def ivs(name, value, _object=@object); _object.instance_variable_set "@#{name}", value; end
522
- 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
523
- 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
524
- def depth?(_model=@model); _model && _model['depth']; end
525
- def before_close?(_model=@model); _model && _model['before_close']; end
526
- def each_before_close?(_model=@model); _model && _model['each_before_close']; end
527
- def compact?(_model=@model); _model && _model['compact']; end
528
- def attach?(_model=@model); _model && _model['attach']; end
529
- def attach_elements?(_model=@model); _model && _model['attach_elements']; end
530
- def attach_attributes?(_model=@model); _model && _model['attach_attributes']; end
531
- def delimiter?(_model=@model); _model && _model['delimiter']; end
532
- def as_name?(_model=@model); _model && _model['as_name']; end
533
- def initialize_with?(_model=@model); _model && _model['initialize_with']; end
534
- def create_accessors?(_model=@model); _model && _model['create_accessors'] && [_model['create_accessors']].flatten.compact; end
535
- def accessor?(_model=@model); _model && _model['accessor'] && [_model['accessor']].flatten.compact; end
536
- def element_handler?(_model=@model); _model && _model['element_handler']; end
537
-
538
-
539
- # Methods for submodel
540
-
541
- # This might be broken.
542
- def label_or_tag(_tag=@tag, new_model=@local_model); as_name?(new_model) || _tag; end
543
-
544
-
545
- def clean_members(obj=@object)
546
- #puts ["CURSOR.clean_members: #{object.class}", "tag: #{tag}", "model-name: #{model[:name]}"]
547
- # cursor.object = clean_member(cursor.object)
548
- # clean_members(ivg(shared_attribute_var, obj))
549
- if obj.is_a?(Hash)
550
- obj.dup.each do |k,v|
551
- obj[k] = clean_member(v)
552
- yield(v) if block_given?
553
- end
554
- elsif obj.is_a?(Array)
555
- obj.dup.each_with_index do |v,i|
556
- obj[i] = clean_member(v)
557
- yield(v) if block_given?
558
- end
559
- else
560
- obj.instance_variables.each do |var|
561
- dat = obj.instance_variable_get(var)
562
- obj.instance_variable_set(var, clean_member(dat))
563
- yield(dat) if block_given?
564
- end
565
- end
566
- # obj.instance_variables.each do |var|
567
- # dat = obj.instance_variable_get(var)
568
- # obj.instance_variable_set(var, clean_member(dat))
569
- # yield(dat) if block_given?
570
- # end
571
- end
572
-
573
- def clean_member(val)
574
- if val.is_a?(Hash) || val.is_a?(Array);
575
- if val && val.empty?
576
- nil
577
- elsif val && val.respond_to?(:values) && val.size == 1
578
- val.values[0]
579
- else
580
- val
581
- end
582
- else
583
- val
584
- # # Probably shouldn't do this on instance-var values. ...Why not?
585
- # if val.instance_variables.size < 1
586
- # nil
587
- # elsif val.instance_variables.size == 1
588
- # val.instance_variable_get(val.instance_variables[0])
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
- # Master method to attach any object to this object.
904
- def _attach_object!(obj, *args) # name/label, collision-delimiter, attachment-prefs, type, *options: <options>
905
- #puts ["\nATTACH_OBJECT._attach_object", "self.class: #{self.class}", "obj.class: #{obj.class}", "obj.to_s: #{obj.to_s}", "args: #{args}"]
906
- options = ATTACH_OBJECT_DEFAULT_OPTIONS.merge(args.last.is_a?(Hash) ? args.pop : {}){|key, old, new| new || old}
907
- # name = (args[0] || options[:name])
908
- # delimiter = (args[1] || options[:delimiter])
909
- prefs = (args[2] || options[:prefs])
910
- # type = (args[3] || options[:type])
911
- return if (prefs=='none' || prefs=='cursor') #['none', 'cursor'].include? prefs ... not sure which is faster.
912
- self._merge_object!(
913
- obj,
914
- args[0] || options[:name] || 'unknown_name',
915
- args[1] || options[:delimiter],
916
- prefs,
917
- args[3] || options[:type],
918
- options
919
- )
920
-
921
- # case
922
- # when prefs=='none' || prefs=='cursor'; nil
923
- # when name
924
- # self._merge_object!(obj, name, delimiter, prefs, type, options)
925
- # else
926
- # self._merge_object!(obj, 'unknown_name', delimiter, prefs, type, options)
927
- # end
928
- #puts ["\nATTACH_OBJECT RESULT", self.to_yaml]
929
- #puts ["\nATTACH_OBJECT RESULT PORTALS", (self.portals.to_yaml rescue 'no portals')]
930
- end
931
-
932
- # Master method to merge any object with this object
933
- def _merge_object!(obj, name, delimiter, prefs, type, options={})
934
- #puts ["\n-----OBJECT._merge_object", self.class, (obj.to_s rescue obj.class), name, delimiter, prefs, type.capitalize, options].join(', ')
935
- if prefs=='private'
936
- _merge_instance!(obj, name, delimiter, prefs, type, options)
937
- else
938
- _merge_shared!(obj, name, delimiter, prefs, type, options)
939
- end
940
- end
941
-
942
- # Merge a named object with the shared instance variable of self.
943
- def _merge_shared!(obj, name, delimiter, prefs, type, options={})
944
- shared_var = instance_variable_get("@#{options[:shared_variable_name]}") || instance_variable_set("@#{options[:shared_variable_name]}", options[:default_class].new)
945
- #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}'"
946
- # TODO: Figure this part out:
947
- # The resetting of shared_variable_name to 'attributes' was to fix Asset.field_controls (it was not able to find the valuelive name).
948
- # I think there might be a level of heirarchy that is without a proper cursor model, when using shared variables & object delimiters.
949
- shared_var._merge_object!(obj, name, delimiter, nil, type, options.merge(:shared_variable_name=>ATTACH_OBJECT_DEFAULT_OPTIONS[:shared_variable_name]))
950
- end
951
-
952
- # Merge a named object with the specified instance variable of self.
953
- def _merge_instance!(obj, name, delimiter, prefs, type, options={})
954
- #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(', ')
955
- rslt = if instance_variable_get("@#{name}") || delimiter
956
- if delimiter
957
- delimit_name = obj._get_attribute(delimiter, options[:shared_variable_name]).to_s.downcase
958
- #puts ["\n_setting_with_delimiter", delimit_name]
959
- #instance_variable_set("@#{name}", instance_variable_get("@#{name}") || options[:default_class].new)[delimit_name]=obj
960
- # This line is more efficient than the above line.
961
- instance_variable_set("@#{name}", options[:default_class].new) unless instance_variable_get("@#{name}")
962
-
963
- # This line was active in 3.0.9.pre01, but it was inserting each portal array as an element in the array,
964
- # after all the Rfm::Record instances had been added. This was causing an potential infinite recursion situation.
965
- # I don't think this line was supposed to be here - was probably an older piece of code.
966
- #instance_variable_get("@#{name}")[delimit_name]=obj
967
-
968
- #instance_variable_get("@#{name}")._merge_object!(obj, delimit_name, nil, nil, nil)
969
- # Trying to handle multiple portals with same table-occurance on same layout.
970
- # In parsing terms, this is trying to handle multiple elements who's delimiter field contains the SAME delimiter data.
971
- instance_variable_get("@#{name}")._merge_delimited_object!(obj, delimit_name)
972
- else
973
- #puts ["\_setting_existing_instance_var", name]
974
- if name == options[:text_label]
975
- instance_variable_get("@#{name}") << obj.to_s
976
- else
977
- instance_variable_set("@#{name}", [instance_variable_get("@#{name}")].flatten << obj)
978
- end
979
- end
980
- else
981
- #puts ["\n_setting_new_instance_var", name]
982
- instance_variable_set("@#{name}", obj)
983
- end
984
-
985
- # NEW
986
- _create_accessor(name) if (options[:create_accessors] & ['all','private']).any?
987
-
988
- rslt
989
- end
990
-
991
- def _merge_delimited_object!(obj, delimit_name)
992
- #puts "MERGING DELIMITED OBJECT self #{self.class} obj #{obj.class} delimit_name #{delimit_name}"
993
-
994
- case
995
- when self[delimit_name].nil?; self[delimit_name] = obj
996
- when self[delimit_name].is_a?(Hash); self[delimit_name].merge!(obj)
997
- when self[delimit_name].is_a?(Array); self[delimit_name] << obj
998
- else self[delimit_name] = [self[delimit_name], obj]
999
- end
1000
- end
1001
-
1002
- # Get an instance variable, a member of a shared instance variable, or a hash value of self.
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
- return unless name
1005
- #puts ["\n\n", self.to_yaml]
1006
- #puts ["OBJECT_get_attribute", self.class, self.instance_variables, name, shared_var_name, options].join(', ')
1007
- (shared_var_name = options[:shared_variable_name]) unless shared_var_name
1008
-
1009
- rslt = case
1010
- when self.is_a?(Hash) && self[name]; self[name]
1011
- when ((var= instance_variable_get("@#{shared_var_name}")) && var[name]); var[name]
1012
- else instance_variable_get("@#{name}")
1013
- end
1014
-
1015
- #puts "OBJECT#_get_attribute: name '#{name}' shared_var_name '#{shared_var_name}' options '#{options}' rslt '#{rslt}'"
1016
- rslt
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
- # # We don't know which attributes are shared, so this isn't really accurate per the options.
1020
- # # But this could be useful for mass-attachment of a set of attributes (to increase performance in some situations).
1021
- # def _create_accessors options=[]
1022
- # options=[options].flatten.compact
1023
- # #puts ['CREATE_ACCESSORS', self.class, options, ""]
1024
- # return false if (options & ['none']).any?
1025
- # if (options & ['all', 'private']).any?
1026
- # meta = (class << self; self; end)
1027
- # meta.send(:attr_reader, *instance_variables.collect{|v| v.to_s[1,99].to_sym})
1028
- # end
1029
- # if (options & ['all', 'shared']).any?
1030
- # instance_variables.collect{|v| instance_variable_get(v)._create_accessors('hash')}
1031
- # end
1032
- # return true
1033
- # end
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
- #puts "OBJECT._create_accessor '#{name}' for Object '#{self.class}'"
1038
- meta = (class << self; self; end)
1039
- meta.send(:attr_reader, name.to_sym)
1040
- end
1041
-
1042
- # Attach hash as individual instance variables to self.
1043
- # This is for manually attaching a hash of attributes to the current object.
1044
- # Pass in translation procs to alter the keys or values.
1045
- def _attach_as_instance_variables(hash, options={})
1046
- #hash.each{|k,v| instance_variable_set("@#{k}", v)} if hash.is_a? Hash
1047
- key_translator = options[:key_translator]
1048
- value_translator = options[:value_translator]
1049
- #puts ["ATTACH_AS_INSTANCE_VARIABLES", key_translator, value_translator].join(', ')
1050
- if hash.is_a? Hash
1051
- hash.each do |k,v|
1052
- (k = key_translator.call(k)) if key_translator
1053
- (v = value_translator.call(k, v)) if value_translator
1054
- instance_variable_set("@#{k}", v)
1055
- end
1056
- end
1057
- end
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
- def _merge_object!(obj, name, delimiter, prefs, type, options={})
1063
- #puts ["\n+++++ARRAY._merge_object", self.class, (obj.to_s rescue obj.class), name, delimiter, prefs, type, options].join(', ')
1064
- case
1065
- when prefs=='shared' || type == 'attribute' && prefs.to_s != 'private' ; _merge_shared!(obj, name, delimiter, prefs, type, options)
1066
- when prefs=='private'; _merge_instance!(obj, name, delimiter, prefs, type, options)
1067
- else self << obj
1068
- end
1069
- end
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
- def _merge_object!(obj, name, delimiter, prefs, type, options={})
1075
- #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}"]
1076
- output = case
1077
- when prefs=='shared'
1078
- _merge_shared!(obj, name, delimiter, prefs, type, options)
1079
- when prefs=='private'
1080
- _merge_instance!(obj, name, delimiter, prefs, type, options)
1081
- when (self[name] || delimiter)
1082
- rslt = if delimiter
1083
- delimit_name = obj._get_attribute(delimiter, options[:shared_variable_name]).to_s.downcase
1084
- #puts "MERGING delimited object with hash: self '#{self.class}' obj '#{obj.class}' name '#{name}' delim '#{delimiter}' delim_name '#{delimit_name}' options '#{options}'"
1085
- self[name] ||= options[:default_class].new
1086
-
1087
- #self[name][delimit_name]=obj
1088
- # This is supposed to handle multiple elements who's delimiter value is the SAME.
1089
- self[name]._merge_delimited_object!(obj, delimit_name)
1090
- else
1091
- if name == options[:text_label]
1092
- self[name] << obj.to_s
1093
- else
1094
- self[name] = [self[name]].flatten
1095
- self[name] << obj
1096
- end
1097
- end
1098
- _create_accessor(name) if (options[:create_accessors].to_a & ['all','shared','hash']).any?
1099
-
1100
- rslt
1101
- else
1102
- rslt = self[name] = obj
1103
- _create_accessor(name) if (options[:create_accessors] & ['all','shared','hash']).any?
1104
- rslt
1105
- end
1106
- #puts ["\n*****HASH._merge_object! RESULT", self.to_yaml]
1107
- #puts ["\n*****HASH._merge_object! RESULT PORTALS", (self.portals.to_yaml rescue 'no portals')]
1108
- output
1109
- end
1110
-
1111
- # def _create_accessors options=[]
1112
- # #puts ['CREATE_ACCESSORS_for_HASH', self.class, options]
1113
- # options=[options].flatten.compact
1114
- # super and
1115
- # if (options & ['all', 'hash']).any?
1116
- # meta = (class << self; self; end)
1117
- # keys.each do |k|
1118
- # meta.send(:define_method, k) do
1119
- # self[k]
1120
- # end
1121
- # end
1122
- # end
1123
- # end
1124
-
1125
- def _create_accessor(name)
1126
- #puts "HASH._create_accessor '#{name}' for Hash '#{self.class}'"
1127
- meta = (class << self; self; end)
1128
- meta.send(:define_method, name) do
1129
- self[name]
1130
- end
1131
- end
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
- # Use variables, not methods.
1198
- # Use string interpolation not concatenation.
1199
- # Use destructive! operations (carefully). Really?
1200
- # Get this book: http://my.safaribooksonline.com/9780321540034?portal=oreilly
1201
- # See this page: http://www.igvita.com/2008/07/08/6-optimization-tips-for-ruby-mri/
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
- # of the non-attached tag.
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.