ginjo-rfm 3.0.4 → 3.0.5

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.
@@ -39,4 +39,9 @@ elements:
39
39
  attach: hash
40
40
  attach_attributes: hash
41
41
  delimiter: text
42
+ # Experimental, but needs to be done.
43
+ # - name: errorcode
44
+ # as_name: error
45
+ # attach: private
46
+ # compact: true
42
47
 
@@ -14,7 +14,7 @@ elements:
14
14
  attach: none
15
15
  - name: errorcode
16
16
  attach: none
17
- before_close: 'check_for_errors'
17
+ before_close: :check_for_errors
18
18
  attributes:
19
19
  - name: text
20
20
  as_name: error
@@ -28,26 +28,42 @@ elements:
28
28
  - name: NAME
29
29
  as_name: name
30
30
  - name: field
31
- # Used to be 'object.meta', but that required sloppy 'loaded' indicator handling (or infinite loop),
32
- # so now just referring to raw inst var @meta, instead of method .meta.
33
- handler: [object.instance_variable_get('@meta'), handle_new_field_control, _attributes]
31
+ attach:
32
+ - cursor
33
+ - Rfm::Metadata::FieldControl
34
+ - :new
35
+ - ivg(:meta)
36
+ # Must use before_close handler to attach, since field_mapping must be applied to value-list key name.
37
+ before_close: [object, ':element_close_handler']
38
+ # # Used to be 'object.meta', but that required sloppy 'loaded' indicator handling (or infinite loop),
39
+ # # so now just referring to raw inst var @meta, instead of method .meta.
40
+ # # Need to assume attributes may or may not be included in start_element call.
41
+ # handler: [object.instance_variable_get('@meta'), handle_new_field_control, _attributes]
42
+ # # attach_attributes isn't doing anything when used with new-element-handler.
34
43
  attach_attributes: private
44
+ delimiter: name
35
45
  elements:
46
+ # This config doesn't use the creation handler, so it should work with ox.
36
47
  - name: style
37
- handler: [object, handle_style_element, _attributes]
38
- # attach: none
39
- # attributes:
40
- # - name: valuelist
41
- # as_name: value_list_name
48
+ #element_handler: [object, handle_style_element, _attributes]
49
+ attach: none
50
+ #attach: [cursor, object, handle_style_element, _attributes]
51
+ #attach_attributes: none
52
+ attributes:
53
+ - name: valuelist
54
+ as_name: value_list_name
55
+ #translate: translate_style_value
42
56
  - name: valuelists
43
57
  attach: none
44
58
  - name: valuelist
45
- class: Array
59
+ #class: Array
60
+ attach: [_meta, Array, ':new']
46
61
  as_name: value_lists
47
62
  delimiter: name
48
63
  elements:
49
64
  - name: value
50
- class: Rfm::Metadata::ValueListItem
65
+ #class: Rfm::Metadata::ValueListItem
66
+ attach: [array, 'Rfm::Metadata::ValueListItem', ':allocate']
51
67
  before_close: replace(@value.to_s)
52
68
  attach_attributes: private
53
69
  attributes:
@@ -16,33 +16,38 @@ elements:
16
16
  - name: product
17
17
  - name: error
18
18
  attach: none
19
- before_close: 'check_for_errors'
19
+ before_close: :check_for_errors
20
20
  attributes:
21
21
  - name: code
22
22
  as_name: error
23
23
  - name: datasource
24
24
  attach: none
25
+ before_close: [object, end_datasource_element_callback, self]
25
26
  attributes:
26
27
  - name: total_count
27
28
  accessor: none
28
- before_close: :end_datasource_element_callback
29
29
  - name: metadata
30
30
  attach: none
31
31
  - name: field_definition
32
- handler: [object.meta, handle_new_field, _attributes]
33
- before_close: :main_callback
32
+ # These two steps can be used to create the attachment to resultset-meta automatically,
33
+ # but the field-mapping translation won't happen.
34
+ # attach: [_meta, 'Rfm::Metadata::Field', allocate]
35
+ # as_name: field_meta
36
+ attach: [cursor, 'Rfm::Metadata::Field', ':allocate']
37
+ delimiter: name
38
+ attach_attributes: private
39
+ before_close: [object, field_definition_element_close_callback, self]
34
40
  - name: relatedset_definition
35
41
  delimiter: table
36
42
  as_name: portal_meta
37
43
  attach_attributes: private
38
44
  elements:
39
45
  - name: field_definition
40
- class: Rfm::Metadata::Field
41
- attach: cursor
46
+ attach: [cursor, 'Rfm::Metadata::Field', ':allocate']
42
47
  delimiter: name
43
48
  as_name: field_meta
44
49
  attach_attributes: private
45
- before_close: :portal_callback
50
+ before_close: [object, relatedset_field_definition_element_close_callback, self]
46
51
  - name: resultset
47
52
  attach: none
48
53
  attributes:
@@ -51,32 +56,33 @@ elements:
51
56
  - name: fetch_size
52
57
  accessor: none
53
58
  - name: record
54
- handler: [object, handle_new_record, _attributes]
59
+ #attach: [cursor, object, handle_new_record, _attributes]
60
+ #attach_attributes: none
61
+ attach: [array, 'Rfm::Record', new, object]
62
+ attach_attributes: private
55
63
  before_close: '@loaded=true'
56
64
  elements:
57
65
  - name: field
58
- class: Rfm::Metadata::Datum
59
- compact: true
60
- attach: cursor
61
- before_close: :handler_callback
66
+ attach: [cursor, 'Rfm::Metadata::Datum', ':allocate']
67
+ compact: false
68
+ before_close: [object, field_element_close_callback, self]
62
69
  - name: relatedset
63
- class: Array
70
+ attach: [private, Array, ':allocate']
64
71
  as_name: portals
65
- attach: private
66
72
  attach_attributes: private
67
73
  create_accessors: all
68
74
  delimiter: table
69
75
  elements:
70
76
  - name: record
71
- class: Rfm::Record
77
+ #class: Rfm::Record
78
+ attach: [default, 'Rfm::Record', ':allocate']
72
79
  attach_attributes: private
73
80
  before_close: '@loaded=true'
74
81
  elements:
75
82
  - name: field
76
83
  compact: true
77
- class: Rfm::Metadata::Datum
78
- attach: cursor
79
- before_close: :portal_callback
84
+ attach: [cursor, 'Rfm::Metadata::Datum', ':allocate']
85
+ before_close: [object, portal_field_element_close_callback, self]
80
86
 
81
87
 
82
88
 
@@ -1,3 +1,7 @@
1
+ # encoding: UTF-8
2
+ # Encoding is necessary for Ox, which appears to ignore character encoding.
3
+ # See: http://stackoverflow.com/questions/11331060/international-chars-using-rspec-with-ruby-on-rails
4
+ #
1
5
  # #### A declarative SAX parser, written by William Richardson #####
2
6
  #
3
7
  # This XML parser builds a result object from callbacks sent by any ruby sax/stream parsing
@@ -31,6 +35,13 @@
31
35
  # Note: 'attach: cursor' puts the object in the cursor & stack but does not attach it to the parent.
32
36
  # 'attach: none' prevents the object from entering the cursor or stack.
33
37
  # Both of these will still allow processing of attributes and subelements.
38
+ #
39
+ # Note: Attribute attachment is controlled first by the attributes' model's :attributes hash (controls individual attrs),
40
+ # and second by the base model's main hash. Any model's main hash :attach_attributes only controls
41
+ # attributes that will be attached to that model's object. So if a model's object is not attached to anything
42
+ # (:attach=>'none'), then the higher base-model's :attach_attributes will control the lower model's attribute attachment.
43
+ # Put another way: If a model is :attach=>'none', then its :attach_attributes won't be counted.
44
+ #
34
45
  #
35
46
  # Examples:
36
47
  # Rfm::SaxParser.parse('some/file.xml') # => defaults to best xml backend with no parsing configuration.
@@ -43,20 +54,23 @@
43
54
  #
44
55
  # YAML structure defining a SAX xml parsing template.
45
56
  # Options:
46
- # initialize: array: initialize new objects with this code [:method, params] instead of defaulting to 'allocate'
57
+ # initialize_with: string, symbol, or array (object, method, params...). Should return new object. See Rfm::SaxParser::Cursor#get_callback.
47
58
  # elements: array of element hashes [{'name'=>'element-tag'},...]
48
59
  # attributes: array of attribute hashes {'name'=>'attribute-name'} UC
49
- # class: NOT USED string-or-class: class name for new element
50
- # depth: integer: depth-of-default-class UC
51
- # attach: string: shared, _shared_name, private, hash, array, cursor, none - attach this element or attribute to parent.
52
- # attach_elements: string: same as 'attach' - for all subelements, unless they have their own 'attach' specification
53
- # attach_attributes: string: same as 'attach' - for all attributes, unless they have their own 'attach' specification
54
- # before_close: symbol (method) or string (code): run a model method before closing tag, passing in #cursor. String is eval'd in context of object.
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
+ # attach_elements: string: same as 'attach' - how to attach ANY subelements to this model's object, unless they have their own 'attach' specification.
63
+ # attach_attributes: string: same as 'attach' - how to attach ANY attributes to this model's object, unless they have their own 'attach' specification.
64
+ # before_close: string, symbol, or array (object, method, params...). See Rfm::SaxParser::Cursor#get_callback.
55
65
  # as_name: string: store element or attribute keyed as specified
56
66
  # delimiter: string: attribute/hash key to delineate objects with identical tags
57
- # create_accessors: UC string or array: all, private, shared, hash, none
58
- # accessor: UC string: all, private, shared, hash, none
59
- # handler: array: call an object with any params [obj, method, params]. Default attach prefs are 'cursor'.
67
+ # create_accessors: string or array: all, private, shared, hash, none
68
+ # accessor: string: all, private, shared, hash, none
69
+ # element_handler: string, symbol, or array (object, method, params...). Should return new object. See Rfm::SaxParser::Cursor#get_callback.
70
+ # Default attach prefs are 'cursor'.
71
+ # Use this when all new-element operations should be offloaded to custom class or module.
72
+ # Should return an instance of new object.
73
+ # 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.
60
74
  #
61
75
  #
62
76
  # #### See below for notes & todos ####
@@ -68,50 +82,58 @@ require 'stringio'
68
82
 
69
83
  module Rfm
70
84
  module SaxParser
71
- extend Forwardable
85
+
86
+ RUBY_VERSION_NUM = RUBY_VERSION[0,3].to_f
72
87
 
73
88
  PARSERS = {}
74
- # OPTIONS constant not yet used.
75
- # OPTIONS = [:name, :elements, :attributes, :attach, :attach_elements, :attach_attributes, :compact,
76
- # :depth, :before_close, :each_before_close, :delimiter, :as_name, :initialize, :handler
77
- # ]
78
- DEFAULTS = [:default_class, :backend, :text_label, :tag_translation, :shared_instance_var, :templates, :template_prefix]
79
89
 
80
-
81
- class << self
82
- attr_accessor *DEFAULTS
83
- end
84
-
85
- ### Default class MUST be a descendant of Hash or respond to hash methods !!!
86
- (@default_class = Hash) unless defined? @default_class
87
-
88
- # Use :libxml, :nokogiri, :ox, :rexml, or anything else, if you want it to always default
90
+ # These defaults can be set here or in any ancestor/enclosing module or class,
91
+ # as long as the defaults or their constants can be seen from this POV.
92
+ #
93
+ # Default class MUST be a descendant of Hash or respond to hash methods !!!
94
+ #
95
+ # For backend, use :libxml, :nokogiri, :ox, :rexml, or anything else, if you want it to always default
89
96
  # to something other than the fastest backend found.
90
- # Nil will let the SaxParser decide.
91
- (@backend = nil) unless defined? @backend
92
-
93
- (@text_label = 'text') unless defined? @text_label
94
-
95
- (@tag_translation = lambda {|txt| txt.gsub(/\-/, '_').downcase}) unless defined? @tag_translation
96
-
97
- (@shared_instance_var = 'attributes') unless defined? @shared_instance_var
98
-
99
- (@templates = {}) unless defined? @templates
97
+ # Using nil will let the SaxParser decide.
98
+ @parser_defaults = {
99
+ :default_class => Hash,
100
+ :backend => nil,
101
+ :text_label => 'text',
102
+ :tag_translation => lambda {|txt| txt.gsub(/\-/, '_').downcase},
103
+ :shared_variable_name => 'attributes',
104
+ :templates => {},
105
+ :template_prefix => nil
106
+ }
100
107
 
101
- def self.parse(*args)
102
- Handler.build(*args)
108
+ # Merge any upper-level default definitions
109
+ if defined? PARSER_DEFAULTS
110
+ tmp_defaults = PARSER_DEFAULTS.dup
111
+ PARSER_DEFAULTS.replace(@parser_defaults).merge!(tmp_defaults)
112
+ else
113
+ PARSER_DEFAULTS = @parser_defaults
103
114
  end
104
115
 
105
- # Installs attribute accessors for defaults
106
- def self.install_defaults(klass)
107
- klass.extend Forwardable
108
- klass.def_delegators SaxParser, *DEFAULTS
109
- class << klass
110
- extend Forwardable
111
- def_delegators SaxParser, *DEFAULTS
112
- end
116
+ # Convert defaults to constants, available to all sub classes/modules/instances.
117
+ PARSER_DEFAULTS.each do |k, v|
118
+ k = k.to_s.upcase
119
+ #(const_set k, v) unless eval("defined? #{k}") #(const_defined?(k) or defined?(k))
120
+ if eval("defined? #{k}")
121
+ (const_set k, eval(k))
122
+ else
123
+ (const_set k, v)
124
+ end
113
125
  end
126
+
127
+ ::Object::ATTACH_OBJECT_DEFAULT_OPTIONS = {
128
+ :shared_variable_name => SHARED_VARIABLE_NAME,
129
+ :default_class => DEFAULT_CLASS,
130
+ :text_label => TEXT_LABEL,
131
+ :create_accessors => [] #:all, :private, :shared, :hash
132
+ }
114
133
 
134
+ def self.parse(*args)
135
+ Handler.build(*args)
136
+ end
115
137
 
116
138
  # A Cursor instance is created for each element encountered in the parsing run
117
139
  # and is where the parsing result is constructed from the custom parsing template.
@@ -125,11 +147,17 @@ module Rfm
125
147
  # you will always get the last object added to the stack. Think of a cursor as
126
148
  # a framework of tools that accompany each element's build process.
127
149
  class Cursor
128
-
129
- #attr_accessor :model, :object, :tag, :parent, :top, :stack, :newtag, :callbacks
130
- attr_accessor :model, :object, :tag, :newtag, :callbacks, :handler, :parent #, :top, :stack,
150
+ extend Forwardable
151
+
152
+ # model - currently active model (rename to current_model)
153
+ # local_model - model of this cursor's tag (rename to local_model)
154
+ # newtag - incoming tag of yet-to-be-created cursor. Get rid of this if you can.
155
+ # element_attachment_prefs - local object's attachment prefs based on local_model and current_model.
156
+ # level - cursor depth
157
+ attr_accessor :model, :local_model, :object, :tag, :handler, :parent, :level, :element_attachment_prefs, :new_element_callback, :initial_attributes #, :newtag
131
158
 
132
- SaxParser.install_defaults(self)
159
+
160
+ #SaxParser.install_defaults(self)
133
161
 
134
162
  def_delegators :handler, :top, :stack
135
163
 
@@ -139,157 +167,234 @@ module Rfm
139
167
 
140
168
  case
141
169
  when klass.is_a?(Class); klass
142
- when (klass=klass.to_s) == ''; default_class
170
+ #when (klass=klass.to_s) == ''; DEFAULT_CLASS
171
+ when klass.nil?; DEFAULT_CLASS
172
+ when klass == ''; DEFAULT_CLASS
143
173
  when klass[/::/]; eval(klass)
144
174
  when defined?(klass); const_get(klass) ## == 'constant'; const_get(klass)
145
- #when defined?(klass); eval(klass) # This was for 'handler' pattern.
175
+ #when defined?(klass); eval(klass) # This was for 'element_handler' pattern.
146
176
  else
147
177
  Rfm.log.warn "Could not find constant '#{klass}'"
148
- default_class
178
+ DEFAULT_CLASS
149
179
  end
150
180
 
151
181
  end
152
182
 
153
- def initialize(_model, _obj, _tag, _handler)
154
- @tag = _tag
155
- @model = _model
156
- @object = _obj #_obj.is_a?(String) ? get_constant(_obj).new : _obj
157
- @callbacks = {}
183
+ def initialize(_tag, _handler, _parent=nil, _initial_attributes=nil) #, caller_binding=nil)
184
+ #def initialize(_model, _obj, _tag, _handler)
185
+ @tag = _tag
158
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'; @element_attachment_prefs = nil; end
198
+ end
199
+
200
+ #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}"]
201
+
159
202
  self
160
203
  end
161
204
 
162
- def handler
163
- @handler
164
- end
165
-
166
-
167
205
 
168
206
  ##### SAX METHODS #####
169
207
 
170
- # Not sure if this is still used.
208
+ # Receive a single attribute (any named attribute or text)
171
209
  def receive_attribute(name, value)
172
- #puts "\nRECEIVE_ATTR '#{name}' value '#{value}' object '#{object.class}' model '#{model.keys}' subm '#{submodel.keys}' tag '#{tag}' newtag '#{newtag}'"
173
- new_att = default_class.new.tap{|att| att[name]=value}
174
- assign_attributes(new_att, object, model, model)
210
+ #puts ["\nRECEIVE_ATTR '#{name}'", "value: #{value}", "tag: #{@tag}", "object: #{object.class}", "model: #{model['name']}"]
211
+ new_att = {name=>value} #.new.tap{|att| att[name]=value}
212
+
213
+ assign_attributes(new_att) #, @object, @model, @local_model)
175
214
  rescue
176
215
  Rfm.log.warn "Error: could not assign attribute '#{name.to_s}' to element '#{self.tag.to_s}': #{$!}"
177
216
  end
178
-
217
+
179
218
  def receive_start_element(_tag, _attributes)
180
- # TODO: Use a case statement to separate the various tasks possible in this method
219
+ #puts ["\nRECEIVE_START '#{_tag}'", "current_object: #{@object.class}", "current_model: #{@model}"]
220
+ new_cursor = Cursor.new(_tag, @handler, self, _attributes) #, binding)
221
+ new_cursor.process_new_element(binding)
222
+
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
+
229
+ #puts ["\nPROCESS_NEW_ELEMENT tag: #{@tag}", "@element_attachment_prefs: #{@element_attachment_prefs}", "@local_model: #{local_model}"]
230
+
231
+ new_element = @new_element_callback ? get_callback(@new_element_callback, caller_binding) : nil
181
232
 
233
+ case
182
234
 
183
- #puts ['START', _tag, object.class, model]
184
- # Set newtag for other methods to use during the start_el run.
185
- @newtag = _tag
186
-
187
- # Acquire submodel definition.
188
- subm = model_elements?(@newtag) || default_class.new
189
-
190
- # Get attachment_prefs
191
- prefs = attachment_prefs(model, subm, 'element')
192
-
193
- #puts "RECEIVE_START_ELEMENT: _tag '#{_tag}'\ncurrent object '#{object.class}'\ncursor_model '#{model}'\ncursor_submodel '#{subm.to_yaml}', attributes '#{_attributes.to_a}'"
194
-
195
- # Clean-up and return if new element is not to be attached.
196
- if prefs == 'none'
197
- # Set callbacks, since object & model of new element won't be stored.
198
- callbacks[_tag] = lambda {run_callback(_tag, self, subm)}
199
- #puts "Assigning attributes for attach:none on #{newtag}"
200
- # This passes current model, since that is the model that will be accepting thiese attributes, if any.
201
- assign_attributes(_attributes, object, model, subm)
202
- return
203
- end
204
-
205
- if handler?(subm)
206
- code = handler?(subm)
207
- obj = eval(code[0].to_s)
208
- mthd = code[1].to_s
209
- prms = eval(code[2].to_s)
210
- new_element = obj.send(mthd, prms)
211
- #puts ["\nIF_HANDLER", code, new_element.class, new_element]
212
- else
213
- # Create new element.
214
- const = get_constant(subm['class'])
215
- # Needs to be duped !!!
216
- init = initialize?(subm) ? initialize?(subm).dup : []
217
- #puts init.to_yaml
218
- init[0] ||= :allocate
219
- init[1] = eval(init[1].to_s)
220
- #puts "Current object: #{eval('object').class}"
221
- #puts "Creating new element '#{const}' with '#{init[0]}' and '#{init[1].class}'"
222
- new_element = const.send(*init.compact)
223
- #puts "Created new element of class '#{new_element.class}' for _tag '#{tag}'."
235
+ # when inital cursor, just set model & object.
236
+ when @tag == '__TOP__';
237
+ #puts "__TOP__"
238
+ @model = @handler.template
239
+ @object = @handler.initial_object
240
+
241
+ when @element_attachment_prefs == 'none';
242
+ #puts "__NONE__"
243
+ @model = @parent.model #nil
244
+ @object = @parent.object #nil
245
+
246
+ if @initial_attributes && @initial_attributes.any? #&& @attribute_attachment_prefs != 'none'
247
+ assign_attributes(@initial_attributes) #, @object, @model, @local_model)
248
+ end
249
+
250
+ when @element_attachment_prefs == 'cursor';
251
+ #puts "__CURSOR__"
252
+ @model = @local_model
253
+ @object = new_element || DEFAULT_CLASS.allocate
254
+
255
+ if @initial_attributes && @initial_attributes.any? #&& @attribute_attachment_prefs != 'none'
256
+ assign_attributes(@initial_attributes) #, @object, @model, @local_model)
257
+ end
258
+
259
+ else
260
+ #puts "__OTHER__"
261
+ @model = @local_model
262
+ @object = new_element || DEFAULT_CLASS.allocate
224
263
 
225
- # Assign attributes to new element.
226
- assign_attributes(_attributes, new_element, subm, subm) #unless attach_attributes?(subm) == 'none'
227
-
228
- # Attach new element to cursor object
229
- attach_new_object(object, new_element, newtag, model, subm, 'element') #unless prefs == 'cursor'
230
- end
231
-
232
- returntag = newtag
233
- self.newtag = nil
234
- Cursor.new(subm, new_element, returntag, handler)
235
- end # start_el
236
-
264
+ if @initial_attributes && @initial_attributes.any? #&& @attribute_attachment_prefs != 'none'
265
+ assign_attributes(@initial_attributes) #, @object, @model, @local_model)
266
+ end
267
+
268
+ if !delimiter?(@local_model)
269
+ #attach_new_object(@parent.object, @object, @tag, @parent.model, @local_model, 'element')
270
+ attach_new_element(@tag, @object)
271
+ end
272
+ end
273
+
274
+ self
275
+ end
276
+
277
+
237
278
  def receive_end_element(_tag)
238
- #puts ["\nEND_ELEMENT #{_tag}", "CurrentObject: #{object.class}", "CurrentTag: #{self.tag}", "CurrentModel: #{model}", "BeforeClose: #{before_close?}", "Compact: #{compact?}"]
279
+ #puts ["\nRECEIVE_END_ELEMENT '#{_tag}'", "tag: #{@tag}", "object: #{@object.class}", "model: #{@model['name']}", "local_model: #{@local_model['name']}"]
280
+ #puts ["\nEND_ELEMENT_OBJECT", object.to_yaml]
239
281
  begin
240
- if _tag == self.tag
282
+
283
+ if _tag == @tag && (@model == @local_model)
241
284
  # Data cleaup
242
- compactor_settings = compact?
243
- (compactor_settings = compact?(top.model)) unless compactor_settings # prefer local settings, or use top settings.
285
+ compactor_settings = compact? || compact?(top.model)
286
+ #(compactor_settings = compact?(top.model)) unless compactor_settings # prefer local settings, or use top settings.
244
287
  (clean_members {|v| clean_members(v){|v| clean_members(v)}}) if compactor_settings
245
288
  end
246
-
247
- # # EXPERIMENTAL: sending modle PLUS submodel prefs to _create_accessors
248
- # # Acquire submodel definition for create accessors (EXPERIMENTAL)
249
- # subm = model_elements?(_tag) || default_class.new
250
- # accessor_options = (create_accessors? | create_accessors?(subm))
251
- # if accessor_options.any?
252
- # #puts ["CREATING_ACCESSORS #{accessor_options}"]
253
- # object._create_accessors(accessor_options)
254
- # end
255
-
256
- # # Create accessors is specified.
257
- # # TODO: This creates redundant calls when elements close with the same model as current. But how to get the correct model when elements close that are attach:none ???
258
- # if create_accessors?.any?
259
- # #puts ['CREATING_ACCESSORS', create_accessors?]
260
- # object._create_accessors(create_accessors?)
261
- # end
262
-
263
- # Run callback of non-stored element.
264
- callbacks[_tag].call if callbacks[_tag]
265
- if _tag == self.tag
289
+
290
+ if (delimiter = delimiter?(@local_model); delimiter && !['none','cursor'].include?(@element_attachment_prefs.to_s))
291
+ #attach_new_object(@parent.object, @object, @tag, @parent.model, @local_model, 'element')
292
+ attach_new_element(@tag, @object)
293
+ end
294
+
295
+ if _tag == @tag #&& (@model == @local_model)
266
296
  # End-element callbacks.
267
- run_callback(_tag, self)
268
- # if before_close?.is_a? Symbol
269
- # object.send before_close?, self
270
- # elsif before_close?.is_a?(String)
271
- # object.send :eval, before_close?
272
- # end
297
+ #run_callback(_tag, self)
298
+ callback = before_close?(@local_model)
299
+ get_callback(callback, binding) if callback
273
300
  end
274
301
 
275
- # return true only if matching tags
276
- if _tag == self.tag
302
+ if _tag == @tag
303
+ # return true only if matching tags
277
304
  return true
278
305
  end
279
- # rescue
280
- # Rfm.log.warn "Error: end_element tag '#{_tag}' failed: #{$!}"
306
+
307
+ # # return true only if matching tags
308
+ # if _tag == @tag
309
+ # return true
310
+ # end
311
+
312
+ return
313
+ # rescue
314
+ # Rfm.log.debug "Error: end_element tag '#{_tag}' failed: #{$!}"
281
315
  end
282
316
  end
283
-
284
- def run_callback(_tag, _cursor=self, _model=_cursor.model, _object=_cursor.object )
285
- callback = before_close?(_model)
286
- #puts ["\nRUN_CALLBACK", _tag, _cursor.tag, _object.class, callback]
287
- if callback.is_a? Symbol
288
- _object.send callback, _cursor
289
- elsif callback.is_a?(String)
290
- _object.send :eval, callback
291
- end
292
- end
317
+
318
+ ### Parse callback instructions, compile & send callback method ###
319
+ # This method will send a method to an object, with parameters, and return a new object.
320
+ # Input: string, symbol, or array of strings
321
+ # Returns: object
322
+ # Default options:
323
+ # :object=>object
324
+ # :method=>'a method name string or symbol'
325
+ # :params=>"params string to be eval'd in context of cursor"
326
+ # Usage:
327
+ # callback: send a method (or eval string) to an object with parameters.
328
+ # string: a string to be eval'd in context of current object.
329
+ # symbol: method to be called on current object.
330
+ # array: object, method, params.
331
+ # object: <object or string>
332
+ # method: <string or symbol>
333
+ # params: <string>
334
+ #
335
+ # TODO-MAYBE: Change param order to (method, object, params),
336
+ # might help confusion with param complexities.
337
+ #
338
+ def get_callback(callback, caller_binding=binding, defaults={})
339
+ input = callback.is_a?(Array) ? callback.dup : callback
340
+ #puts "\nGET_CALLBACK tag: #{tag}, callback: #{callback}"
341
+ params = case
342
+ when input.is_a?(String) || input.is_a?(Symbol)
343
+ [nil, input]
344
+ # when input.is_a?(Symbol)
345
+ # [nil, input]
346
+ when input.is_a?(Array)
347
+ #puts ["\nCURSOR#get_callback is an array", input]
348
+ case
349
+ when input[0].is_a?(Symbol)
350
+ [nil, input].flatten(1)
351
+ when input[1].is_a?(String) && ( input.size > 2 || (remove_colon=(input[1][0,1]==":"); remove_colon) )
352
+ code_or_method = input[1].dup
353
+ code_or_method[0]='' if remove_colon
354
+ code_or_method = code_or_method.to_sym
355
+ output = [input[0], code_or_method, input[2..-1]].flatten(1)
356
+ #puts ["\nCURSOR#get_callback converted input[1] to symbol", output]
357
+ output
358
+ else # when input is ['object', 'sym-or-str', 'param1',' param2', ...]
359
+ input
360
+ end
361
+ else
362
+ []
363
+ end
364
+
365
+ obj_raw = params.shift
366
+ #puts ["\nOBJECT_RAW:","class: #{obj_raw.class}", "object: #{obj_raw}"]
367
+ obj = if obj_raw.is_a?(String); eval(obj_raw.to_s, caller_binding); else obj_raw; end
368
+ if obj.nil? || obj == ''; obj = defaults[:object] || @object; end
369
+ #puts ["\nOBJECT:","class: #{obj.class}", "object: #{obj}"]
370
+
371
+ code = params.shift || defaults[:method]
372
+ params.each_with_index{|str,i| if str.is_a?(String); params[i] = eval(str, caller_binding); end }
373
+ params = defaults[:params] if params.size == 0
374
+ #puts ["\nGET_CALLBACK tag: #{@tag}" ,"callback: #{callback}", "obj.class: #{obj.class}", "code: #{code}", "params-class #{params.class}"]
375
+ case
376
+ when (code.nil? || code=='')
377
+ obj
378
+ when (code.is_a?(Symbol) || params)
379
+ #puts ["\nGET_CALLBACK sending symbol", obj.class, code]
380
+ obj.send *[code, params].flatten(1).compact
381
+ when code.is_a?(String)
382
+ #puts ["\nGET_CALLBACK evaling string", obj.class, code]
383
+ obj.send :eval, code
384
+ #eval(code, caller_binding)
385
+ end
386
+ end
387
+
388
+ # # Run before-close callback.
389
+ # def run_callback(_tag, _cursor=self, _model=_cursor.local_model, _object=_cursor.object )
390
+ # callback = before_close?(_model)
391
+ # #puts ["\nRUN_CALLBACK", _tag, _cursor.tag, _object.class, callback, callback.class]
392
+ # if callback.is_a? Symbol
393
+ # _object.send callback, _cursor
394
+ # elsif callback.is_a?(String)
395
+ # _object.send :eval, callback
396
+ # end
397
+ # end
293
398
 
294
399
 
295
400
 
@@ -297,52 +402,91 @@ module Rfm
297
402
  ##### MERGE METHODS #####
298
403
 
299
404
  # Assign attributes to element.
300
- def assign_attributes(_attributes, base_object, base_model, new_model)
405
+ def assign_attributes(_attributes)
301
406
  if _attributes && !_attributes.empty?
302
407
 
303
- #puts ["\nASSIGN_ATTRIBUTES", base_object.class, base_model, new_model].join(' **** ')
304
-
305
- # # This is trying to merge element set as a whole, if possible.
306
- # # Experimental #
307
- # #
308
- # # OLD: prefs_exist = model_attributes?(nil, new_model) || attach_attributes?(base_model) || delimiter?(base_model)
309
- # attribute_prefs = attach_attributes?(base_model)
310
- # case
311
- # when !model_attributes?(nil, base_model) && attribute_prefs.to_s[/shared|_/]
312
- # shared_var_name = shared_variable_name(attribute_prefs) || 'attributes'
313
- # #puts ["MASS", attribute_prefs, shared_var_name, _attributes.keys].join(', ')
314
- # #attach_new_object(base_object, _attributes, shared_var_name, base_model, new_model, 'attribute')
315
- # #base_object._attach_object!(_attributes, shared_var_name, nil, 'private', 'attribute', :default_class=>default_class, :create_accessors=>create_accessors?(base_model))
316
- # (var = ivg(shared_var_name, base_object)) ? var.merge!(_attributes) : ivs(shared_var_name, _attributes, base_object)
317
- # else
318
- # #puts "Loading attributes individually."
319
- # #puts ["INDIVIDUAL", attribute_prefs, _attributes.keys].join(', ')
320
- _attributes.each{|k,v| attach_new_object(base_object, v, k, base_model, model_attributes?(k, new_model), 'attribute')}
321
- # end
408
+ _attributes.each do |k,v|
409
+ #attach_new_object(base_object, v, k, base_model, model_attributes?(k, new_model), 'attribute')}
410
+ attr_model = model_attributes?(k, @local_model)
411
+
412
+ label = label_or_tag(k, attr_model)
413
+
414
+ prefs = [attachment_prefs(@model, attr_model, 'attribute')].flatten(1)[0]
415
+
416
+ shared_var_name = shared_variable_name(prefs)
417
+ (prefs = "shared") if shared_var_name
418
+
419
+ # Use local create_accessors prefs first, then more general ones.
420
+ create_accessors = accessor?(attr_model) || create_accessors?(@model)
421
+ #(create_accessors = create_accessors?(@model)) unless create_accessors && create_accessors.any?
422
+
423
+ #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}"]
424
+ @object._attach_object!(v, label, delimiter?(attr_model), prefs, 'attribute', :default_class=>DEFAULT_CLASS, :shared_variable_name=>shared_var_name, :create_accessors=>create_accessors)
425
+ end
426
+
322
427
  end
323
428
  end
324
429
 
325
- def attach_new_object(base_object, new_object, name, base_model, new_model, type)
326
- label = label_or_tag(name, new_model)
430
+ # def attach_new_object(base_object, new_object, name, base_model, new_model, type)
431
+ # label = label_or_tag(name, new_model)
432
+ #
433
+ # # Was this, which works fine, but not as efficient:
434
+ # # prefs = [attachment_prefs(base_model, new_model, type)].flatten(1)[0]
435
+ # prefs = if type=='attribute'
436
+ # [attachment_prefs(base_model, new_model, type)].flatten(1)[0]
437
+ # else
438
+ # @element_attachment_prefs
439
+ # end
440
+ #
441
+ # shared_var_name = shared_variable_name(prefs)
442
+ # (prefs = "shared") if shared_var_name
443
+ #
444
+ # # Use local create_accessors prefs first, then more general ones.
445
+ # create_accessors = accessor?(new_model)
446
+ # (create_accessors = create_accessors?(base_model)) unless create_accessors && create_accessors.any?
447
+ #
448
+ # # # This is NEW!
449
+ # # translator = new_model['translator']
450
+ # # if translator
451
+ # # new_object = base_object.send translator, name, new_object
452
+ # # end
453
+ #
454
+ #
455
+ # #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}"]
456
+ # 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)
457
+ # #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]
458
+ # # if type == 'attribute'
459
+ # # 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}"]
460
+ # # end
461
+ # end
462
+
463
+ def attach_new_element(name, new_object) #(base_object, new_object, name, base_model, new_model, type)
464
+ label = label_or_tag(name, @local_model)
327
465
 
328
- prefs = attachment_prefs(base_model, new_model, type)
466
+ # Was this, which works fine, but not as efficient:
467
+ # prefs = [attachment_prefs(base_model, new_model, type)].flatten(1)[0]
468
+ prefs = @element_attachment_prefs
329
469
 
330
- # shared_var_name = nil
331
- # if prefs.to_s[0,1] == "_"
332
- # shared_var_name = prefs.to_s[1,16]
333
- # prefs = "shared"
334
- # end
335
- shared_var_name = shared_variable_name prefs
470
+ shared_var_name = shared_variable_name(prefs)
336
471
  (prefs = "shared") if shared_var_name
337
472
 
338
473
  # Use local create_accessors prefs first, then more general ones.
339
- create_accessors = accessor?(new_model)
340
- (create_accessors = create_accessors?(base_model)) unless create_accessors && create_accessors.any?
474
+ create_accessors = accessor?(@local_model) || create_accessors?(@parent.model)
475
+ #(create_accessors = create_accessors?(@parent.model)) unless create_accessors && create_accessors.any?
476
+
477
+ # # This is NEW!
478
+ # translator = new_model['translator']
479
+ # if translator
480
+ # new_object = base_object.send translator, name, new_object
481
+ # end
341
482
 
342
483
 
343
- #puts ["\nCURSOR.attach_new_object 1", type, label, base_object.class, new_object.class, delimiter?(new_model), prefs, shared_var_name, create_accessors].join(', ')
344
- 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)
345
- #puts ["\nCURSOR.attach_new_object 2: #{base_object.class} with ", label, delimiter?(new_model), prefs, type, :default_class=>default_class, :shared_variable_name=>shared_var_name, :create_accessors=>create_accessors]
484
+ #puts ["\nATTACH_NEW_ELEMENT 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}"]
485
+ @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)
486
+ #puts ["\nATTACH_NEW_ELEMENT 2: #{base_object.class} with ", label, delimiter?(new_model), prefs, type, :shared_variable_name=>shared_var_name, :create_accessors=>create_accessors]
487
+ # if type == 'attribute'
488
+ # 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}"]
489
+ # end
346
490
  end
347
491
 
348
492
  def attachment_prefs(base_model, new_model, type)
@@ -355,7 +499,7 @@ module Rfm
355
499
  def shared_variable_name(prefs)
356
500
  rslt = nil
357
501
  if prefs.to_s[0,1] == "_"
358
- rslt = prefs.to_s[1,16] #prefs.gsub(/^_/, '')
502
+ rslt = prefs.to_s[1..-1] #prefs.gsub(/^_/, '')
359
503
  end
360
504
  end
361
505
 
@@ -367,30 +511,32 @@ module Rfm
367
511
  end
368
512
 
369
513
  # Methods for current _model
370
- def ivg(name, _object=object); _object.instance_variable_get "@#{name}"; end
371
- def ivs(name, value, _object=object); _object.instance_variable_set "@#{name}", value; end
372
- 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
373
- 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
374
- def depth?(_model=model); _model && _model['depth']; end
375
- def before_close?(_model=model); _model && _model['before_close']; end
376
- def each_before_close?(_model=model); _model && _model['each_before_close']; end
377
- def compact?(_model=model); _model && _model['compact']; end
378
- def attach?(_model=model); _model && _model['attach']; end
379
- def attach_elements?(_model=model); _model && _model['attach_elements']; end
380
- def attach_attributes?(_model=model); _model && _model['attach_attributes']; end
381
- def delimiter?(_model=model); _model && _model['delimiter']; end
382
- def as_name?(_model=model); _model && _model['as_name']; end
383
- def initialize?(_model=model); _model && _model['initialize']; end
384
- def create_accessors?(_model=model); _model && [_model['create_accessors']].flatten.compact; end
385
- def accessor?(_model=model); _model && [_model['accessor']].flatten.compact; end
386
- def handler?(_model=model); _model && _model['handler']; end
514
+ def ivg(name, _object=@object); _object.instance_variable_get "@#{name}"; end
515
+ def ivs(name, value, _object=@object); _object.instance_variable_set "@#{name}", value; end
516
+ 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
517
+ 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
518
+ def depth?(_model=@model); _model && _model['depth']; end
519
+ def before_close?(_model=@model); _model && _model['before_close']; end
520
+ def each_before_close?(_model=@model); _model && _model['each_before_close']; end
521
+ def compact?(_model=@model); _model && _model['compact']; end
522
+ def attach?(_model=@model); _model && _model['attach']; end
523
+ def attach_elements?(_model=@model); _model && _model['attach_elements']; end
524
+ def attach_attributes?(_model=@model); _model && _model['attach_attributes']; end
525
+ def delimiter?(_model=@model); _model && _model['delimiter']; end
526
+ def as_name?(_model=@model); _model && _model['as_name']; end
527
+ def initialize_with?(_model=@model); _model && _model['initialize_with']; end
528
+ def create_accessors?(_model=@model); _model && _model['create_accessors'] && [_model['create_accessors']].flatten.compact; end
529
+ def accessor?(_model=@model); _model && _model['accessor'] && [_model['accessor']].flatten.compact; end
530
+ def element_handler?(_model=@model); _model && _model['element_handler']; end
387
531
 
388
532
 
389
533
  # Methods for submodel
390
- def label_or_tag(_tag=newtag, new_model=submodel); as_name?(new_model) || _tag; end
534
+
535
+ # This might be broken.
536
+ def label_or_tag(_tag=@tag, new_model=@local_model); as_name?(new_model) || _tag; end
391
537
 
392
538
 
393
- def clean_members(obj=object)
539
+ def clean_members(obj=@object)
394
540
  #puts ["CURSOR.clean_members: #{object.class}", "tag: #{tag}", "model-name: #{model[:name]}"]
395
541
  # cursor.object = clean_member(cursor.object)
396
542
  # clean_members(ivg(shared_attribute_var, obj))
@@ -460,23 +606,23 @@ module Rfm
460
606
  # is returned to the object that originally called for the parsing run (your script/app/whatever).
461
607
  module Handler
462
608
 
463
- attr_accessor :stack, :template
609
+ attr_accessor :stack, :template, :initial_object, :stack_debug
464
610
 
465
- SaxParser.install_defaults(self)
611
+ #SaxParser.install_defaults(self)
466
612
 
467
613
 
468
614
  ### Class Methods ###
469
615
 
470
616
  # Main parsing interface (also aliased at SaxParser.parse)
471
617
  def self.build(io, template=nil, initial_object=nil, parser=nil, options={})
472
- parser = parser || options[:parser] || backend
618
+ parser = parser || options[:parser] || BACKEND
473
619
  parser = get_backend(parser)
474
620
  (Rfm.log.info "Using backend parser: #{parser}, with template: #{template}") if options[:log_parser]
475
621
  parser.build(io, template, initial_object)
476
622
  end
477
623
 
478
624
  def self.included(base)
479
- # Add a .build method to the custom handler class, when the generic Handler module is included.
625
+ # Add a .build method to the custom handler instance, when the generic Handler module is included.
480
626
  def base.build(io, template=nil, initial_object=nil)
481
627
  handler = new(template, initial_object)
482
628
  handler.run_parser(io)
@@ -485,7 +631,7 @@ module Rfm
485
631
  end # self.included
486
632
 
487
633
  # Takes backend symbol and returns custom Handler class for specified backend.
488
- def self.get_backend(parser=backend)
634
+ def self.get_backend(parser=BACKEND)
489
635
  (parser = decide_backend) unless parser
490
636
  if parser.is_a?(String) || parser.is_a?(Symbol)
491
637
  parser_proc = PARSERS[parser.to_sym][:proc]
@@ -508,21 +654,17 @@ module Rfm
508
654
 
509
655
  ### Instance Methods ###
510
656
 
511
- def initialize(_template=nil, initial_object=nil)
512
- initial_object = case
513
- when initial_object.nil?; default_class.new
514
- when initial_object.is_a?(Class); initial_object.new
515
- when initial_object.is_a?(String) || initial_object.is_a?(Symbol); SaxParser.get_constant(initial_object).new
516
- else initial_object
657
+ def initialize(_template=nil, _initial_object=nil)
658
+ @initial_object = case
659
+ when _initial_object.nil?; DEFAULT_CLASS.new
660
+ when _initial_object.is_a?(Class); _initial_object.new
661
+ when _initial_object.is_a?(String) || _initial_object.is_a?(Symbol); SaxParser.get_constant(_initial_object).new
662
+ else _initial_object
517
663
  end
518
- #initial_object = initial_object || default_class.new || {}
519
664
  @stack = []
665
+ @stack_debug=[]
520
666
  @template = get_template(_template)
521
- @tag_translation = tag_translation
522
- #(@template = @template.values[0]) if @template.size == 1
523
- #y @template
524
- init_element_buffer
525
- set_cursor Cursor.new(@template, initial_object, 'top', self)
667
+ set_cursor Cursor.new('__TOP__', self).process_new_element
526
668
  end
527
669
 
528
670
  # Takes string, symbol, hash, and returns a (possibly cached) parsing template.
@@ -538,12 +680,13 @@ module Rfm
538
680
  # end
539
681
  # (templates[name] = rslt) #unless dat == rslt
540
682
  # The above works, but this is cleaner.
541
- templates[name] = templates[name] && load_template(templates[name]) || load_template(name)
683
+ TEMPLATES[name] = TEMPLATES[name] && load_template(TEMPLATES[name]) || load_template(name)
542
684
  end
543
685
 
544
686
  # Does the heavy-lifting of template retrieval.
545
687
  def load_template(dat)
546
- prefix = defined?(template_prefix) ? template_prefix : ''
688
+ prefix = defined?(TEMPLATE_PREFIX) ? TEMPLATE_PREFIX : ''
689
+ #puts "SaxParser::Handler#load_template... 'prefix' is #{prefix}"
547
690
  rslt = case
548
691
  when dat.is_a?(Hash); dat
549
692
  when dat.to_s[/\.y.?ml$/i]; (YAML.load_file(File.join(*[prefix, dat].compact)))
@@ -551,7 +694,7 @@ module Rfm
551
694
  when dat.to_s[/\.xml$/i]; self.class.build(File.join(*[prefix, dat].compact), nil, {'compact'=>true})
552
695
  when dat.to_s[/^<.*>/i]; "Convert from xml to Hash - under construction"
553
696
  when dat.is_a?(String); YAML.load dat
554
- else default_class.new
697
+ else DEFAULT_CLASS.new
555
698
  end
556
699
  end
557
700
 
@@ -566,10 +709,7 @@ module Rfm
566
709
  def set_cursor(args) # cursor_object
567
710
  if args.is_a? Cursor
568
711
  stack.push(args)
569
- cursor.parent = stack[-2] || stack[0] #_stack[0] so methods called on parent won't bomb.
570
- # Cursor is no longer storing top or stack, it is delegating those mehthods to main handler.
571
- #cursor.top = stack[0]
572
- #cursor.stack = stack
712
+ #@stack_debug.push(args.dup.tap(){|c| c.handler = c.handler.object_id; c.parent = c.parent.tag})
573
713
  end
574
714
  cursor
575
715
  end
@@ -583,64 +723,49 @@ module Rfm
583
723
  end
584
724
 
585
725
  def transform(name)
586
- return name unless @tag_translation.is_a?(Proc)
587
- #name.to_s.gsub(*@tag_translation)
588
- @tag_translation.call(name.to_s)
589
- end
590
-
591
- def init_element_buffer
592
- @element_buffer = {:tag=>nil, :attributes=>default_class.new, :text=>''}
726
+ return name unless TAG_TRANSLATION.is_a?(Proc)
727
+ TAG_TRANSLATION.call(name.to_s)
593
728
  end
594
-
595
- def send_element_buffer
596
- if element_buffer?
597
- (@element_buffer[:attributes][text_label] = @element_buffer[:text]) if @element_buffer[:text].to_s[/[^\s]/]
598
- set_cursor cursor.receive_start_element(@element_buffer[:tag], @element_buffer[:attributes])
599
- init_element_buffer
600
- end
601
- end
602
-
603
- def element_buffer?
604
- @element_buffer[:tag] && !@element_buffer[:tag].empty?
605
- end
606
-
607
729
 
608
730
  # Add a node to an existing element.
609
731
  def _start_element(tag, attributes=nil, *args)
610
732
  #puts ["_START_ELEMENT", tag, attributes, args].to_yaml # if tag.to_s.downcase=='fmrestulset'
611
733
  tag = transform tag
612
- send_element_buffer if element_buffer?
613
734
  if attributes
614
735
  # This crazy thing transforms attribute keys to underscore (or whatever).
615
736
  #attributes = default_class[*attributes.collect{|k,v| [transform(k),v] }.flatten]
616
737
  # This works but downcases all attribute names - not good.
617
- attributes = default_class.new.tap {|hash| attributes.each {|k, v| hash[transform(k)] = v}}
738
+ attributes = DEFAULT_CLASS.new.tap {|hash| attributes.each {|k, v| hash[transform(k)] = v}}
618
739
  # This doesn't work yet, but at least it wont downcase hash keys.
619
740
  #attributes = Hash.new.tap {|hash| attributes.each {|k, v| hash[transform(k)] = v}}
620
741
  end
621
- @element_buffer.merge!({:tag=>tag, :attributes => attributes || default_class.new})
742
+ set_cursor cursor.receive_start_element(tag, attributes)
622
743
  end
623
744
 
624
745
  # Add attribute to existing element.
625
746
  def _attribute(name, value, *args)
626
747
  #puts "Receiving attribute '#{name}' with value '#{value}'"
627
748
  name = transform name
628
- new_att = default_class.new.tap{|att| att[name]=value}
629
- @element_buffer[:attributes].merge!(new_att)
749
+ cursor.receive_attribute(name, value)
630
750
  end
631
751
 
632
752
  # Add 'content' attribute to existing element.
633
753
  def _text(value, *args)
634
754
  #puts "Receiving text '#{value}'"
635
- return unless value.to_s[/[^\s]/]
636
- @element_buffer[:text] << value
755
+ #puts RUBY_VERSION_NUM
756
+ if RUBY_VERSION_NUM > 1.8 && value.is_a?(String)
757
+ #puts "Forcing utf-8"
758
+ value.force_encoding('UTF-8')
759
+ end
760
+ # I think the reason this was here is no longer relevant, so I'm disabeling.
761
+ return unless value[/[^\s]/]
762
+ cursor.receive_attribute(TEXT_LABEL, value)
637
763
  end
638
764
 
639
765
  # Close out an existing element.
640
766
  def _end_element(tag, *args)
641
767
  tag = transform tag
642
768
  #puts "Receiving end_element '#{tag}'"
643
- send_element_buffer
644
769
  cursor.receive_end_element(tag) and dump_cursor
645
770
  end
646
771
 
@@ -767,30 +892,34 @@ class Object
767
892
 
768
893
  # Master method to attach any object to this object.
769
894
  def _attach_object!(obj, *args) # name/label, collision-delimiter, attachment-prefs, type, *options: <options>
770
- #puts ["OBJECT._attach_object", self.class, obj.class, obj.to_s, args].to_yaml
771
- default_options = {
772
- :shared_variable_name => 'attributes',
773
- :default_class => Hash,
774
- :create_accessors => [] #:all, :private, :shared, :hash
775
- }
776
- options = default_options.merge(args.last.is_a?(Hash) ? args.pop : {}){|key, old, new| new || old}
777
- name = (args[0] || options[:name])
778
- delimiter = (args[1] || options[:delimiter])
895
+ #puts ["\nATTACH_OBJECT._attach_object", self.class, obj.class, obj.to_s, args].to_yaml
896
+ options = ATTACH_OBJECT_DEFAULT_OPTIONS.merge(args.last.is_a?(Hash) ? args.pop : {}){|key, old, new| new || old}
897
+ # name = (args[0] || options[:name])
898
+ # delimiter = (args[1] || options[:delimiter])
779
899
  prefs = (args[2] || options[:prefs])
780
- type = (args[3] || options[:type])
781
- #puts ['OBJECT.attach_object', type, name, self.class, obj.class, delimiter, prefs, options[:shared_variable_name], options[:default_class], options[:create_accessors]].join(', ')
782
- case
783
- when prefs=='none' || prefs=='cursor'; nil
784
- when name
785
- self._merge_object!(obj, name, delimiter, prefs, type, options)
786
- else
787
- self._merge_object!(obj, 'unknown_name', delimiter, prefs, type, options)
788
- end
900
+ # type = (args[3] || options[:type])
901
+ return if (prefs=='none' || prefs=='cursor') #['none', 'cursor'].include? prefs ... not sure which is faster.
902
+ self._merge_object!(
903
+ obj,
904
+ args[0] || options[:name] || 'unknown_name',
905
+ args[1] || options[:delimiter],
906
+ prefs,
907
+ args[3] || options[:type],
908
+ options
909
+ )
910
+
911
+ # case
912
+ # when prefs=='none' || prefs=='cursor'; nil
913
+ # when name
914
+ # self._merge_object!(obj, name, delimiter, prefs, type, options)
915
+ # else
916
+ # self._merge_object!(obj, 'unknown_name', delimiter, prefs, type, options)
917
+ # end
789
918
  end
790
919
 
791
920
  # Master method to merge any object with this object
792
921
  def _merge_object!(obj, name, delimiter, prefs, type, options={})
793
- #puts ["-----OBJECT._merge_object", self.class, (obj.to_s rescue obj.class), name, delimiter, prefs, type.capitalize, options].join(', ')
922
+ #puts ["\n-----OBJECT._merge_object", self.class, (obj.to_s rescue obj.class), name, delimiter, prefs, type.capitalize, options].join(', ')
794
923
  if prefs=='private'
795
924
  _merge_instance!(obj, name, delimiter, prefs, type, options)
796
925
  else
@@ -805,16 +934,16 @@ class Object
805
934
  # TODO: Figure this part out:
806
935
  # The resetting of shared_variable_name to 'attributes' was to fix Asset.field_controls (it was not able to find the valuelive name).
807
936
  # I think there might be a level of heirarchy that is without a proper cursor model, when using shared variables & object delimiters.
808
- shared_var._merge_object!(obj, name, delimiter, nil, type, options.merge(:shared_variable_name=>'attributes'))
937
+ shared_var._merge_object!(obj, name, delimiter, nil, type, options.merge(:shared_variable_name=>ATTACH_OBJECT_DEFAULT_OPTIONS[:shared_variable_name]))
809
938
  end
810
939
 
811
940
  # Merge a named object with the specified instance variable of self.
812
941
  def _merge_instance!(obj, name, delimiter, prefs, type, options={})
813
- #puts ['_merge_instance!', self.class, obj.class, name, delimiter, prefs, type, options.keys, '_end_merge_instance!'].join(', ')
814
- if instance_variable_get("@#{name}") || delimiter
942
+ #puts ["\n_merge_instance!", self.class, obj.class, name, delimiter, prefs, type, options.keys, '_end_merge_instance!'].join(', ')
943
+ rslt = if instance_variable_get("@#{name}") || delimiter
815
944
  if delimiter
816
945
  delimit_name = obj._get_attribute(delimiter, options[:shared_variable_name]).to_s.downcase
817
- #puts ['_setting_with_delimiter', delimit_name]
946
+ #puts ["\n_setting_with_delimiter", delimit_name]
818
947
  #instance_variable_set("@#{name}", instance_variable_get("@#{name}") || options[:default_class].new)[delimit_name]=obj
819
948
  # This line is more efficient than the above line.
820
949
  instance_variable_set("@#{name}", options[:default_class].new) unless instance_variable_get("@#{name}")
@@ -823,28 +952,34 @@ class Object
823
952
  # In parsing terms, this is trying to handle multiple elements who's delimiter field contains the SAME delimiter data.
824
953
  #instance_variable_get("@#{name}")._merge_object!(obj, delimit_name, nil, nil, nil)
825
954
  else
826
- #puts ['_setting_existing_instance_var', name]
827
- instance_variable_set("@#{name}", [instance_variable_get("@#{name}")].flatten << obj)
955
+ #puts ["\_setting_existing_instance_var", name]
956
+ if name == options[:text_label]
957
+ instance_variable_get("@#{name}") << obj.to_s
958
+ else
959
+ instance_variable_set("@#{name}", [instance_variable_get("@#{name}")].flatten << obj)
960
+ end
828
961
  end
829
962
  else
830
- #puts ['_setting_new_instance_var', name]
963
+ #puts ["\n_setting_new_instance_var", name]
831
964
  instance_variable_set("@#{name}", obj)
832
965
  end
833
966
 
834
967
  # NEW
835
968
  _create_accessor(name) if (options[:create_accessors] & ['all','private']).any?
836
969
 
970
+ rslt
837
971
  end
838
972
 
839
973
  # Get an instance variable, a member of a shared instance variable, or a hash value of self.
840
974
  def _get_attribute(name, shared_var_name=nil, options={})
841
975
  return unless name
842
- #puts ["\nOBJECT_get_attribute", self.instance_variables, name, shared_var_name, options].join(', ')
976
+ #puts ["\n\n", self.to_yaml]
977
+ #puts ["OBJECT_get_attribute", self.class, self.instance_variables, name, shared_var_name, options].join(', ')
843
978
  (shared_var_name = options[:shared_variable_name]) unless shared_var_name
844
979
 
845
980
  rslt = case
846
981
  when self.is_a?(Hash) && self[name]; self[name]
847
- when (var= instance_variable_get("@#{shared_var_name}")) && var[name]; var[name]
982
+ when ((var= instance_variable_get("@#{shared_var_name}")) && var[name]); var[name]
848
983
  else instance_variable_get("@#{name}")
849
984
  end
850
985
 
@@ -853,6 +988,7 @@ class Object
853
988
  end
854
989
 
855
990
  # # We don't know which attributes are shared, so this isn't really accurate per the options.
991
+ # # But this could be useful for mass-attachment of a set of attributes (to increase performance in some situations).
856
992
  # def _create_accessors options=[]
857
993
  # options=[options].flatten.compact
858
994
  # #puts ['CREATE_ACCESSORS', self.class, options, ""]
@@ -874,7 +1010,7 @@ class Object
874
1010
  meta.send(:attr_reader, name.to_sym)
875
1011
  end
876
1012
 
877
- # Attach hash as individual instance variables.
1013
+ # Attach hash as individual instance variables to self.
878
1014
  # This is for manually attaching a hash of attributes to the current object.
879
1015
  # Pass in translation procs to alter the keys or values.
880
1016
  def _attach_as_instance_variables(hash, options={})
@@ -885,7 +1021,7 @@ class Object
885
1021
  if hash.is_a? Hash
886
1022
  hash.each do |k,v|
887
1023
  (k = key_translator.call(k)) if key_translator
888
- (v = value_translator.call(v)) if value_translator
1024
+ (v = value_translator.call(k, v)) if value_translator
889
1025
  instance_variable_set("@#{k}", v)
890
1026
  end
891
1027
  end
@@ -895,7 +1031,7 @@ end # Object
895
1031
 
896
1032
  class Array
897
1033
  def _merge_object!(obj, name, delimiter, prefs, type, options={})
898
- #puts ["\n+++++ARRAY._merge_object", self.class, (obj.to_s rescue obj.class), name, delimiter, prefs, type.titleize, options].join(', ')
1034
+ #puts ["\n+++++ARRAY._merge_object", self.class, (obj.to_s rescue obj.class), name, delimiter, prefs, type, options].join(', ')
899
1035
  case
900
1036
  when prefs=='shared' || type == 'attribute' && prefs.to_s != 'private' ; _merge_shared!(obj, name, delimiter, prefs, type, options)
901
1037
  when prefs=='private'; _merge_instance!(obj, name, delimiter, prefs, type, options)
@@ -906,14 +1042,14 @@ end # Array
906
1042
 
907
1043
  class Hash
908
1044
  def _merge_object!(obj, name, delimiter, prefs, type, options={})
909
- #puts ["\n*****HASH._merge_object", type, name, self.class, (obj.to_s rescue obj.class), delimiter, prefs, options].join(', ')
1045
+ #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}"]
910
1046
  case
911
1047
  when prefs=='shared'
912
1048
  _merge_shared!(obj, name, delimiter, prefs, type, options)
913
1049
  when prefs=='private'
914
1050
  _merge_instance!(obj, name, delimiter, prefs, type, options)
915
1051
  when (self[name] || delimiter)
916
- if delimiter
1052
+ rslt = if delimiter
917
1053
  delimit_name = obj._get_attribute(delimiter, options[:shared_variable_name]).to_s.downcase
918
1054
  #puts "MERGING delimited object with hash: self '#{self.class}' obj '#{obj.class}' name '#{name}' delim '#{delimiter}' delim_name '#{delimit_name}' options '#{options}'"
919
1055
  self[name] ||= options[:default_class].new
@@ -922,14 +1058,21 @@ class Hash
922
1058
  #obj.instance_variable_set(:@_index_, 0)
923
1059
  #self[name]._merge_object!(obj, delimit_name, nil, nil, nil)
924
1060
  else
925
- self[name] = [self[name]].flatten
926
- #obj.instance_variable_set(:@_index_, self[name].last.instance_variable_get(:@_index_).to_i + 1)
927
- self[name] << obj
1061
+ if name == options[:text_label]
1062
+ self[name] << obj.to_s
1063
+ else
1064
+ self[name] = [self[name]].flatten
1065
+ self[name] << obj
1066
+ end
928
1067
  end
929
1068
  _create_accessor(name) if (options[:create_accessors] & ['all','shared','hash']).any?
1069
+
1070
+ rslt
930
1071
  else
931
- self[name] = obj
1072
+ rslt = self[name] = obj
932
1073
  _create_accessor(name) if (options[:create_accessors] & ['all','shared','hash']).any?
1074
+ #puts ["\nRESULT", self.to_yaml]
1075
+ rslt
933
1076
  end
934
1077
  end
935
1078