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.
- checksums.yaml +8 -8
- data/CHANGELOG.md +15 -0
- data/lib/rfm.rb +19 -16
- data/lib/rfm/VERSION +1 -1
- data/lib/rfm/metadata/datum.rb +7 -4
- data/lib/rfm/metadata/field.rb +32 -17
- data/lib/rfm/metadata/field_control.rb +44 -46
- data/lib/rfm/metadata/layout_meta.rb +10 -5
- data/lib/rfm/metadata/resultset_meta.rb +15 -6
- data/lib/rfm/record.rb +1 -1
- data/lib/rfm/resultset.rb +8 -6
- data/lib/rfm/utilities/sax/fmpxml_minimal.yml +5 -0
- data/lib/rfm/utilities/sax/fmpxmllayout.yml +27 -11
- data/lib/rfm/utilities/sax/fmresultset.yml +24 -18
- data/lib/rfm/utilities/sax_parser.rb +451 -308
- metadata +16 -2
@@ -14,7 +14,7 @@ elements:
|
|
14
14
|
attach: none
|
15
15
|
- name: errorcode
|
16
16
|
attach: none
|
17
|
-
before_close:
|
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
|
-
|
32
|
-
|
33
|
-
|
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
|
-
|
38
|
-
|
39
|
-
#
|
40
|
-
#
|
41
|
-
|
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:
|
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
|
-
|
33
|
-
|
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
|
-
|
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:
|
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
|
-
|
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
|
-
|
59
|
-
compact:
|
60
|
-
|
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
|
-
|
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
|
-
|
78
|
-
|
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
|
-
#
|
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:
|
50
|
-
#
|
51
|
-
#
|
52
|
-
#
|
53
|
-
#
|
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:
|
58
|
-
# accessor:
|
59
|
-
#
|
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
|
-
|
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
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
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
|
-
#
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
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
|
-
|
102
|
-
|
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
|
-
#
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
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
|
-
|
130
|
-
|
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
|
-
|
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) == '';
|
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 '
|
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
|
-
|
178
|
+
DEFAULT_CLASS
|
149
179
|
end
|
150
180
|
|
151
181
|
end
|
152
182
|
|
153
|
-
def initialize(
|
154
|
-
|
155
|
-
@
|
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
|
-
#
|
208
|
+
# Receive a single attribute (any named attribute or text)
|
171
209
|
def receive_attribute(name, value)
|
172
|
-
#puts "\nRECEIVE_ATTR '#{name}' value
|
173
|
-
new_att =
|
174
|
-
|
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
|
-
|
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
|
-
#
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
#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
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
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 ["\
|
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
|
-
|
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
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
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
|
-
|
269
|
-
|
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
|
-
|
276
|
-
|
302
|
+
if _tag == @tag
|
303
|
+
# return true only if matching tags
|
277
304
|
return true
|
278
305
|
end
|
279
|
-
|
280
|
-
#
|
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
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
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
|
405
|
+
def assign_attributes(_attributes)
|
301
406
|
if _attributes && !_attributes.empty?
|
302
407
|
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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?(
|
340
|
-
(create_accessors = create_accessors?(
|
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 ["\
|
344
|
-
|
345
|
-
#puts ["\
|
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
|
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
|
371
|
-
def ivs(name, value, _object
|
372
|
-
def model_elements?(which=nil, _model
|
373
|
-
def model_attributes?(which=nil, _model
|
374
|
-
def depth?(_model
|
375
|
-
def before_close?(_model
|
376
|
-
def each_before_close?(_model
|
377
|
-
def compact?(_model
|
378
|
-
def attach?(_model
|
379
|
-
def attach_elements?(_model
|
380
|
-
def attach_attributes?(_model
|
381
|
-
def delimiter?(_model
|
382
|
-
def as_name?(_model
|
383
|
-
def
|
384
|
-
def create_accessors?(_model
|
385
|
-
def accessor?(_model
|
386
|
-
def
|
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
|
-
|
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
|
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] ||
|
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
|
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=
|
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,
|
512
|
-
initial_object = case
|
513
|
-
when
|
514
|
-
when
|
515
|
-
when
|
516
|
-
else
|
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
|
-
|
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
|
-
|
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?(
|
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
|
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
|
-
|
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
|
587
|
-
|
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 =
|
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
|
-
|
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
|
-
|
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
|
-
|
636
|
-
|
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 ["
|
771
|
-
|
772
|
-
|
773
|
-
|
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
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
|
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=>
|
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 [
|
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 [
|
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 [
|
827
|
-
|
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 [
|
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 ["\
|
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
|
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]
|
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
|
-
|
926
|
-
|
927
|
-
|
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
|
|