ginjo-rfm 3.0.9 → 3.0.10

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