sax-machine 0.0.16 → 1.3.2
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 +7 -0
- data/.gitignore +10 -0
- data/.rspec +1 -1
- data/.travis.yml +32 -0
- data/Gemfile +13 -2
- data/Guardfile +5 -0
- data/HISTORY.md +77 -0
- data/README.md +207 -0
- data/Rakefile +6 -18
- data/lib/sax-machine/config/sax_ancestor.rb +17 -0
- data/lib/sax-machine/config/sax_attribute.rb +18 -0
- data/lib/sax-machine/config/sax_collection.rb +33 -0
- data/lib/sax-machine/config/sax_element.rb +65 -0
- data/lib/sax-machine/config/sax_element_value.rb +23 -0
- data/lib/sax-machine/handlers/sax_abstract_handler.rb +199 -0
- data/lib/sax-machine/handlers/sax_nokogiri_handler.rb +23 -0
- data/lib/sax-machine/handlers/sax_oga_handler.rb +39 -0
- data/lib/sax-machine/handlers/sax_ox_handler.rb +56 -0
- data/lib/sax-machine/sax_config.rb +49 -18
- data/lib/sax-machine/sax_configure.rb +33 -0
- data/lib/sax-machine/sax_document.rb +105 -50
- data/lib/sax-machine/version.rb +3 -0
- data/lib/sax-machine.rb +27 -7
- data/sax-machine.gemspec +20 -0
- data/spec/fixtures/atom-content.html +15 -0
- data/spec/fixtures/atom.xml +165 -0
- data/spec/sax-machine/sax_activerecord_spec.rb +21 -0
- data/spec/sax-machine/sax_configure_spec.rb +51 -0
- data/spec/sax-machine/sax_document_spec.rb +862 -181
- data/spec/sax-machine/sax_include_spec.rb +49 -0
- data/spec/spec_helper.rb +18 -8
- metadata +73 -69
- data/Gemfile.lock +0 -20
- data/README.textile +0 -87
- data/lib/sax-machine/sax_collection_config.rb +0 -45
- data/lib/sax-machine/sax_element_config.rb +0 -65
- data/lib/sax-machine/sax_handler.rb +0 -67
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
require 'time'
|
|
2
|
+
|
|
3
|
+
module SAXMachine
|
|
4
|
+
module SAXAbstractHandler
|
|
5
|
+
NO_BUFFER = :no_buffer
|
|
6
|
+
|
|
7
|
+
class StackNode < Struct.new(:object, :config, :buffer)
|
|
8
|
+
def initialize(object, config = nil, buffer = NO_BUFFER)
|
|
9
|
+
self.object = object
|
|
10
|
+
self.config = config
|
|
11
|
+
self.buffer = buffer
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def sax_parse(xml_input)
|
|
16
|
+
raise NotImplementedError
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def _initialize(object, on_error = nil, on_warning = nil)
|
|
20
|
+
@stack = [ StackNode.new(object) ]
|
|
21
|
+
@parsed_configs = {}
|
|
22
|
+
@on_error = on_error
|
|
23
|
+
@on_warning = on_warning
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def _characters(data)
|
|
27
|
+
node = stack.last
|
|
28
|
+
|
|
29
|
+
if node.buffer == NO_BUFFER
|
|
30
|
+
node.buffer = data.dup
|
|
31
|
+
else
|
|
32
|
+
node.buffer << data
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def _start_element(name, attrs = [])
|
|
37
|
+
name = normalize_name(name)
|
|
38
|
+
node = stack.last
|
|
39
|
+
object = node.object
|
|
40
|
+
|
|
41
|
+
sax_config = sax_config_for(object)
|
|
42
|
+
|
|
43
|
+
if sax_config
|
|
44
|
+
attrs = Hash[attrs]
|
|
45
|
+
|
|
46
|
+
if collection_config = sax_config.collection_config(name, attrs)
|
|
47
|
+
object = collection_config.data_class.new
|
|
48
|
+
sax_config = sax_config_for(object)
|
|
49
|
+
|
|
50
|
+
stack.push(StackNode.new(object, collection_config))
|
|
51
|
+
|
|
52
|
+
set_attributes_on(object, attrs)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
sax_config.element_configs_for_attribute(name, attrs).each do |ec|
|
|
56
|
+
unless parsed_config?(object, ec)
|
|
57
|
+
object.send(ec.setter, ec.value_from_attrs(attrs))
|
|
58
|
+
mark_as_parsed(object, ec)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
if !collection_config && element_config = sax_config.element_config_for_tag(name, attrs)
|
|
63
|
+
new_object =
|
|
64
|
+
case element_config.data_class.to_s
|
|
65
|
+
when "Integer" then 0
|
|
66
|
+
when "Float" then 0.0
|
|
67
|
+
when "Symbol" then nil
|
|
68
|
+
when "Time" then Time.at(0)
|
|
69
|
+
when "" then object
|
|
70
|
+
else
|
|
71
|
+
element_config.data_class.new
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
stack.push(StackNode.new(new_object, element_config))
|
|
75
|
+
|
|
76
|
+
set_attributes_on(new_object, attrs)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def _end_element(name)
|
|
82
|
+
name = normalize_name(name)
|
|
83
|
+
|
|
84
|
+
start_tag = stack[-2]
|
|
85
|
+
close_tag = stack[-1]
|
|
86
|
+
|
|
87
|
+
return unless start_tag && close_tag
|
|
88
|
+
|
|
89
|
+
object = start_tag.object
|
|
90
|
+
element = close_tag.object
|
|
91
|
+
config = close_tag.config
|
|
92
|
+
value = close_tag.buffer
|
|
93
|
+
|
|
94
|
+
return unless config.name == name
|
|
95
|
+
|
|
96
|
+
unless parsed_config?(object, config)
|
|
97
|
+
if (element_value_config = element_values_for(config))
|
|
98
|
+
element_value_config.each { |evc| element.send(evc.setter, value) }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
if config.respond_to?(:accessor)
|
|
102
|
+
subconfig = sax_config_for(element)
|
|
103
|
+
|
|
104
|
+
if econf = subconfig.element_config_for_tag(name, [])
|
|
105
|
+
element.send(econf.setter, value) unless econf.value_configured?
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
object.send(config.accessor) << element
|
|
109
|
+
else
|
|
110
|
+
value = data_class_value(config.data_class, value) || element
|
|
111
|
+
object.send(config.setter, value) if value != NO_BUFFER
|
|
112
|
+
mark_as_parsed(object, config)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# try to set the ancestor
|
|
116
|
+
if (sax_config = sax_config_for(element))
|
|
117
|
+
sax_config.ancestors.each do |ancestor|
|
|
118
|
+
element.send(ancestor.setter, object)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
stack.pop
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def _error(string)
|
|
127
|
+
if @on_error
|
|
128
|
+
@on_error.call(string)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def _warning(string)
|
|
133
|
+
if @on_warning
|
|
134
|
+
@on_warning.call(string)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
private
|
|
139
|
+
|
|
140
|
+
def mark_as_parsed(object, element_config)
|
|
141
|
+
unless element_config.collection?
|
|
142
|
+
@parsed_configs[[object.object_id, element_config.object_id]] = true
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def parsed_config?(object, element_config)
|
|
147
|
+
@parsed_configs[[object.object_id, element_config.object_id]]
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def sax_config_for(object)
|
|
151
|
+
if object.class.respond_to?(:sax_config)
|
|
152
|
+
object.class.sax_config
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def element_values_for(config)
|
|
157
|
+
if config.data_class.respond_to?(:sax_config)
|
|
158
|
+
config.data_class.sax_config.element_values_for_element
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def normalize_name(name)
|
|
163
|
+
name.to_s.tr("-", "_")
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def set_attributes_on(object, attributes)
|
|
167
|
+
config = sax_config_for(object)
|
|
168
|
+
|
|
169
|
+
if config
|
|
170
|
+
config.attribute_configs_for_element(attributes).each do |ac|
|
|
171
|
+
value = data_class_value(ac.data_class, ac.value_from_attrs(attributes))
|
|
172
|
+
object.send(ac.setter, value)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def data_class_value(data_class, value)
|
|
178
|
+
case data_class.to_s
|
|
179
|
+
when "String" then value != NO_BUFFER ? value.to_s : value
|
|
180
|
+
when "Integer" then value != NO_BUFFER ? value.to_i : value
|
|
181
|
+
when "Float" then value != NO_BUFFER ? value.to_s.gsub(",",".").to_f : value
|
|
182
|
+
when "Symbol" then
|
|
183
|
+
if value != NO_BUFFER
|
|
184
|
+
value.to_s.empty? ? nil : value.to_s.downcase.to_sym
|
|
185
|
+
else
|
|
186
|
+
value
|
|
187
|
+
end
|
|
188
|
+
# Assumes that time elements will be string-based and are not
|
|
189
|
+
# something else, e.g. seconds since epoch
|
|
190
|
+
when "Time" then value != NO_BUFFER ? Time.parse(value.to_s) : value
|
|
191
|
+
when "" then value
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def stack
|
|
196
|
+
@stack
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require 'sax-machine/handlers/sax_abstract_handler'
|
|
2
|
+
require 'nokogiri'
|
|
3
|
+
|
|
4
|
+
module SAXMachine
|
|
5
|
+
class SAXNokogiriHandler < Nokogiri::XML::SAX::Document
|
|
6
|
+
include SAXAbstractHandler
|
|
7
|
+
|
|
8
|
+
def sax_parse(xml_input)
|
|
9
|
+
parser = Nokogiri::XML::SAX::Parser.new(self)
|
|
10
|
+
parser.parse(xml_input) do |ctx|
|
|
11
|
+
ctx.replace_entities = true
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
alias_method :initialize, :_initialize
|
|
16
|
+
alias_method :characters, :_characters
|
|
17
|
+
alias_method :cdata_block, :_characters
|
|
18
|
+
alias_method :start_element, :_start_element
|
|
19
|
+
alias_method :end_element, :_end_element
|
|
20
|
+
alias_method :error, :_error
|
|
21
|
+
alias_method :warning, :_warning
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require 'sax-machine/handlers/sax_abstract_handler'
|
|
2
|
+
require 'oga'
|
|
3
|
+
|
|
4
|
+
module SAXMachine
|
|
5
|
+
class SAXOgaHandler
|
|
6
|
+
include SAXAbstractHandler
|
|
7
|
+
|
|
8
|
+
def initialize(*args)
|
|
9
|
+
_initialize(*args)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def sax_parse(xml_input)
|
|
13
|
+
Oga.sax_parse_xml(self, xml_input)
|
|
14
|
+
rescue LL::ParserError => e
|
|
15
|
+
on_error(e.message)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def on_element(namespace, name, attrs)
|
|
19
|
+
_start_element(node_name(namespace, name), attrs)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def after_element(namespace, name)
|
|
23
|
+
_end_element(node_name(namespace, name))
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def on_error(*args)
|
|
27
|
+
_error(args.join(" "))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
alias_method :on_text, :_characters
|
|
31
|
+
alias_method :on_cdata, :_characters
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def node_name(namespace, name)
|
|
36
|
+
namespace ? "#{namespace}:#{name}" : name
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
require 'sax-machine/handlers/sax_abstract_handler'
|
|
2
|
+
require 'ox'
|
|
3
|
+
|
|
4
|
+
module SAXMachine
|
|
5
|
+
class SAXOxHandler < Ox::Sax
|
|
6
|
+
include SAXAbstractHandler
|
|
7
|
+
|
|
8
|
+
def initialize(*args)
|
|
9
|
+
_initialize(*args)
|
|
10
|
+
_reset_element
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def sax_parse(xml_input)
|
|
14
|
+
# Ox requires input to be streamable
|
|
15
|
+
xml_input = StringIO.new(xml_input) if xml_input.is_a?(String)
|
|
16
|
+
|
|
17
|
+
Ox.sax_parse(self, xml_input,
|
|
18
|
+
symbolize: false,
|
|
19
|
+
convert_special: true,
|
|
20
|
+
skip: :skip_return,
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def attr(name, str)
|
|
25
|
+
@attrs[name] = str
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def attrs_done
|
|
29
|
+
_start_element(@element, @attrs)
|
|
30
|
+
_reset_element
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def start_element(name)
|
|
34
|
+
@element = name
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def text(value)
|
|
38
|
+
_characters(value) if value && !value.empty?
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
alias_method :cdata, :text
|
|
42
|
+
|
|
43
|
+
def error(message, line, column)
|
|
44
|
+
_error("#{message} on line #{line} column #{column}")
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
alias_method :end_element, :_end_element
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def _reset_element
|
|
52
|
+
@attrs = {}
|
|
53
|
+
@element = ""
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -1,47 +1,78 @@
|
|
|
1
|
-
require "sax-machine/
|
|
2
|
-
require "sax-machine/
|
|
1
|
+
require "sax-machine/config/sax_element_value"
|
|
2
|
+
require "sax-machine/config/sax_attribute"
|
|
3
|
+
require "sax-machine/config/sax_element"
|
|
4
|
+
require "sax-machine/config/sax_collection"
|
|
5
|
+
require "sax-machine/config/sax_ancestor"
|
|
3
6
|
|
|
4
7
|
module SAXMachine
|
|
5
8
|
class SAXConfig
|
|
6
|
-
attr_accessor :top_level_elements, :collection_elements
|
|
7
|
-
|
|
9
|
+
attr_accessor :top_level_elements, :top_level_attributes, :top_level_element_value, :collection_elements, :ancestors
|
|
10
|
+
|
|
8
11
|
def initialize
|
|
9
|
-
|
|
10
|
-
@
|
|
12
|
+
# Default value is an empty array
|
|
13
|
+
@top_level_elements = Hash.new { |hash, key| hash[key] = [] }
|
|
14
|
+
@top_level_attributes = []
|
|
15
|
+
@top_level_element_value = []
|
|
16
|
+
@collection_elements = Hash.new { |hash, key| hash[key] = [] }
|
|
17
|
+
@ancestors = []
|
|
11
18
|
end
|
|
12
|
-
|
|
19
|
+
|
|
13
20
|
def columns
|
|
14
|
-
@top_level_elements.map {|
|
|
21
|
+
@top_level_elements.map { |_, ecs| ecs }.flatten
|
|
15
22
|
end
|
|
16
|
-
|
|
23
|
+
|
|
17
24
|
def initialize_copy(sax_config)
|
|
25
|
+
super
|
|
26
|
+
|
|
18
27
|
@top_level_elements = sax_config.top_level_elements.clone
|
|
28
|
+
@top_level_attributes = sax_config.top_level_attributes.clone
|
|
29
|
+
@top_level_element_value = sax_config.top_level_element_value.clone
|
|
19
30
|
@collection_elements = sax_config.collection_elements.clone
|
|
31
|
+
@ancestors = sax_config.ancestors.clone
|
|
20
32
|
end
|
|
21
33
|
|
|
22
34
|
def add_top_level_element(name, options)
|
|
23
|
-
@top_level_elements[name.to_s] = [] unless @top_level_elements[name.to_s]
|
|
24
35
|
@top_level_elements[name.to_s] << ElementConfig.new(name, options)
|
|
25
36
|
end
|
|
26
37
|
|
|
38
|
+
def add_top_level_attribute(name, options)
|
|
39
|
+
@top_level_attributes << AttributeConfig.new(options.delete(:name), options)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def add_top_level_element_value(name, options)
|
|
43
|
+
@top_level_element_value << ElementValueConfig.new(options.delete(:name), options)
|
|
44
|
+
end
|
|
45
|
+
|
|
27
46
|
def add_collection_element(name, options)
|
|
28
|
-
@collection_elements[name.to_s] = [] unless @collection_elements[name.to_s]
|
|
29
47
|
@collection_elements[name.to_s] << CollectionConfig.new(name, options)
|
|
30
48
|
end
|
|
31
49
|
|
|
50
|
+
def add_ancestor(name, options)
|
|
51
|
+
@ancestors << AncestorConfig.new(name, options)
|
|
52
|
+
end
|
|
53
|
+
|
|
32
54
|
def collection_config(name, attrs)
|
|
33
|
-
|
|
34
|
-
|
|
55
|
+
@collection_elements[name.to_s].detect { |cc| cc.attrs_match?(attrs) }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def attribute_configs_for_element(attrs)
|
|
59
|
+
@top_level_attributes.select { |aa| aa.attrs_match?(attrs) }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def element_values_for_element
|
|
63
|
+
@top_level_element_value
|
|
35
64
|
end
|
|
36
65
|
|
|
37
66
|
def element_configs_for_attribute(name, attrs)
|
|
38
|
-
|
|
39
|
-
|
|
67
|
+
return [] unless @top_level_elements.has_key?(name.to_s)
|
|
68
|
+
|
|
69
|
+
@top_level_elements[name.to_s].select { |ec| ec.has_value_and_attrs_match?(attrs) }
|
|
40
70
|
end
|
|
41
71
|
|
|
42
72
|
def element_config_for_tag(name, attrs)
|
|
43
|
-
|
|
44
|
-
|
|
73
|
+
return unless @top_level_elements.has_key?(name.to_s)
|
|
74
|
+
|
|
75
|
+
@top_level_elements[name.to_s].detect { |ec| ec.attrs_match?(attrs) }
|
|
45
76
|
end
|
|
46
77
|
end
|
|
47
|
-
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module SAXMachine
|
|
2
|
+
def self.configure(clazz)
|
|
3
|
+
extended_clazz = Class.new(clazz)
|
|
4
|
+
extended_clazz.send(:include, SAXMachine)
|
|
5
|
+
|
|
6
|
+
# override create_attr to create attributes on the original class
|
|
7
|
+
def extended_clazz.create_attr real_name
|
|
8
|
+
superclass.send(:attr_reader, real_name) unless superclass.method_defined?(real_name)
|
|
9
|
+
superclass.send(:attr_writer, real_name) unless superclass.method_defined?("#{real_name}=")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
yield(extended_clazz)
|
|
13
|
+
|
|
14
|
+
clazz.extend LightWeightSaxMachine
|
|
15
|
+
clazz.sax_config = extended_clazz.sax_config
|
|
16
|
+
|
|
17
|
+
(class << clazz;self;end).send(:define_method, :parse) do |xml_input|
|
|
18
|
+
extended_clazz.parse(xml_input)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
module LightWeightSaxMachine
|
|
23
|
+
attr_writer :sax_config
|
|
24
|
+
|
|
25
|
+
def sax_config
|
|
26
|
+
@sax_config ||= SAXConfig.new
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def inherited(subclass)
|
|
30
|
+
subclass.sax_config.send(:initialize_copy, self.sax_config)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -1,33 +1,96 @@
|
|
|
1
|
-
require "nokogiri"
|
|
2
|
-
|
|
3
1
|
module SAXMachine
|
|
4
|
-
|
|
5
2
|
def self.included(base)
|
|
6
|
-
base.
|
|
3
|
+
base.send(:include, InstanceMethods)
|
|
4
|
+
base.extend(ClassMethods)
|
|
7
5
|
end
|
|
8
|
-
|
|
9
|
-
def parse(
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
|
|
7
|
+
def parse(xml_input, on_error = nil, on_warning = nil)
|
|
8
|
+
handler_klass = SAXMachine.const_get("SAX#{SAXMachine.handler.capitalize}Handler")
|
|
9
|
+
|
|
10
|
+
handler = handler_klass.new(self, on_error, on_warning)
|
|
11
|
+
handler.sax_parse(xml_input)
|
|
12
|
+
|
|
13
13
|
self
|
|
14
14
|
end
|
|
15
|
-
|
|
15
|
+
|
|
16
|
+
module InstanceMethods
|
|
17
|
+
def initialize(attributes = {})
|
|
18
|
+
attributes.each do |name, value|
|
|
19
|
+
send("#{name}=", value)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
self.class.sax_config.top_level_elements.each do |_, configs|
|
|
23
|
+
configs.each do |config|
|
|
24
|
+
next if config.default.nil?
|
|
25
|
+
next unless send(config.as).nil?
|
|
26
|
+
|
|
27
|
+
send(config.setter, config.default)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
16
33
|
module ClassMethods
|
|
34
|
+
def inherited(subclass)
|
|
35
|
+
subclass.sax_config.send(:initialize_copy, self.sax_config)
|
|
36
|
+
end
|
|
17
37
|
|
|
18
|
-
def parse(
|
|
19
|
-
new.parse(
|
|
38
|
+
def parse(*args)
|
|
39
|
+
new.parse(*args)
|
|
20
40
|
end
|
|
21
|
-
|
|
22
|
-
def element(name, options = {})
|
|
23
|
-
options[:as] ||= name
|
|
41
|
+
|
|
42
|
+
def element(name, options = {}, &block)
|
|
43
|
+
real_name = (options[:as] ||= name).to_s
|
|
24
44
|
sax_config.add_top_level_element(name, options)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
45
|
+
create_attr(real_name, &block)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def attribute(name, options = {}, &block)
|
|
49
|
+
real_name = (options[:as] ||= name).to_s
|
|
50
|
+
sax_config.add_top_level_attribute(self.class.to_s, options.merge(name: name))
|
|
51
|
+
create_attr(real_name, &block)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def value(name, options = {}, &block)
|
|
55
|
+
real_name = (options[:as] ||= name).to_s
|
|
56
|
+
sax_config.add_top_level_element_value(self.class.to_s, options.merge(name: name))
|
|
57
|
+
create_attr(real_name, &block)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def ancestor(name, options = {}, &block)
|
|
61
|
+
real_name = (options[:as] ||= name).to_s
|
|
62
|
+
sax_config.add_ancestor(name, options)
|
|
63
|
+
create_attr(real_name, &block)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def elements(name, options = {}, &block)
|
|
67
|
+
real_name = (options[:as] ||= name).to_s
|
|
68
|
+
|
|
69
|
+
if options[:class]
|
|
70
|
+
sax_config.add_collection_element(name, options)
|
|
71
|
+
else
|
|
72
|
+
if block_given?
|
|
73
|
+
define_method("add_#{real_name}") do |value|
|
|
74
|
+
send(real_name).send(:<<, instance_exec(value, &block))
|
|
75
|
+
end
|
|
76
|
+
else
|
|
77
|
+
define_method("add_#{real_name}") do |value|
|
|
78
|
+
send(real_name).send(:<<, value)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
sax_config.add_top_level_element(name, options.merge(collection: true))
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
if !method_defined?(real_name)
|
|
86
|
+
class_eval <<-SRC
|
|
87
|
+
def #{real_name}
|
|
88
|
+
@#{real_name} ||= []
|
|
89
|
+
end
|
|
90
|
+
SRC
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
attr_writer(options[:as]) unless method_defined?("#{options[:as]}=")
|
|
31
94
|
end
|
|
32
95
|
|
|
33
96
|
def columns
|
|
@@ -35,7 +98,7 @@ module SAXMachine
|
|
|
35
98
|
end
|
|
36
99
|
|
|
37
100
|
def column(sym)
|
|
38
|
-
columns.select{|c| c.column == sym}[0]
|
|
101
|
+
columns.select { |c| c.column == sym }[0]
|
|
39
102
|
end
|
|
40
103
|
|
|
41
104
|
def data_class(sym)
|
|
@@ -47,36 +110,28 @@ module SAXMachine
|
|
|
47
110
|
end
|
|
48
111
|
|
|
49
112
|
def column_names
|
|
50
|
-
columns.map{|e| e.column}
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def elements(name, options = {})
|
|
54
|
-
options[:as] ||= name
|
|
55
|
-
if options[:class]
|
|
56
|
-
sax_config.add_collection_element(name, options)
|
|
57
|
-
else
|
|
58
|
-
class_eval <<-SRC
|
|
59
|
-
def add_#{options[:as]}(value)
|
|
60
|
-
#{options[:as]} << value
|
|
61
|
-
end
|
|
62
|
-
SRC
|
|
63
|
-
sax_config.add_top_level_element(name, options.merge(:collection => true))
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
if !instance_methods.include?(options[:as].to_s)
|
|
67
|
-
class_eval <<-SRC
|
|
68
|
-
def #{options[:as]}
|
|
69
|
-
@#{options[:as]} ||= []
|
|
70
|
-
end
|
|
71
|
-
SRC
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
attr_writer options[:as] unless instance_methods.include?("#{options[:as]}=")
|
|
113
|
+
columns.map { |e| e.column }
|
|
75
114
|
end
|
|
76
|
-
|
|
115
|
+
|
|
77
116
|
def sax_config
|
|
78
117
|
@sax_config ||= SAXConfig.new
|
|
79
118
|
end
|
|
119
|
+
|
|
120
|
+
# we only want to insert the getter and setter if they haven't defined it from elsewhere.
|
|
121
|
+
# this is how we allow custom parsing behavior. So you could define the setter
|
|
122
|
+
# and have it parse the string into a date or whatever.
|
|
123
|
+
def create_attr(real_name, &block)
|
|
124
|
+
attr_reader(real_name) unless method_defined?(real_name)
|
|
125
|
+
|
|
126
|
+
if !method_defined?("#{real_name}=")
|
|
127
|
+
if block_given?
|
|
128
|
+
define_method("#{real_name}=") do |value|
|
|
129
|
+
instance_variable_set("@#{real_name}", instance_exec(value, &block))
|
|
130
|
+
end
|
|
131
|
+
else
|
|
132
|
+
attr_writer(real_name)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
80
136
|
end
|
|
81
|
-
|
|
82
|
-
end
|
|
137
|
+
end
|
data/lib/sax-machine.rb
CHANGED
|
@@ -1,11 +1,31 @@
|
|
|
1
|
-
require "
|
|
2
|
-
|
|
3
|
-
$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__))) unless $LOAD_PATH.include?(File.expand_path(File.dirname(__FILE__)))
|
|
4
|
-
|
|
1
|
+
require "sax-machine/version"
|
|
5
2
|
require "sax-machine/sax_document"
|
|
6
|
-
require "sax-machine/
|
|
3
|
+
require "sax-machine/sax_configure"
|
|
7
4
|
require "sax-machine/sax_config"
|
|
8
5
|
|
|
9
6
|
module SAXMachine
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
def self.handler
|
|
8
|
+
@@handler ||= nil
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.handler=(handler)
|
|
12
|
+
if handler
|
|
13
|
+
require "sax-machine/handlers/sax_#{handler}_handler"
|
|
14
|
+
@@handler = handler
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Try handlers
|
|
20
|
+
[:ox, :oga].each do |handler|
|
|
21
|
+
begin
|
|
22
|
+
SAXMachine.handler = handler
|
|
23
|
+
break
|
|
24
|
+
rescue LoadError
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Still no handler, use Nokogiri
|
|
29
|
+
if SAXMachine.handler.nil?
|
|
30
|
+
SAXMachine.handler = :nokogiri
|
|
31
|
+
end
|
data/sax-machine.gemspec
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
require File.expand_path("../lib/sax-machine/version", __FILE__)
|
|
3
|
+
|
|
4
|
+
Gem::Specification.new do |s|
|
|
5
|
+
s.name = "sax-machine"
|
|
6
|
+
s.version = SAXMachine::VERSION
|
|
7
|
+
|
|
8
|
+
s.authors = ["Paul Dix", "Julien Kirch", "Ezekiel Templin", "Dmitry Krasnoukhov"]
|
|
9
|
+
s.email = %q{paul@pauldix.net}
|
|
10
|
+
s.homepage = %q{http://github.com/pauldix/sax-machine}
|
|
11
|
+
s.summary = %q{Declarative SAX Parsing with Nokogiri, Ox or Oga}
|
|
12
|
+
s.license = %q{MIT}
|
|
13
|
+
|
|
14
|
+
s.files = `git ls-files`.split("\n")
|
|
15
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
16
|
+
s.require_paths = ["lib"]
|
|
17
|
+
s.platform = Gem::Platform::RUBY
|
|
18
|
+
|
|
19
|
+
s.add_development_dependency "rspec", "~> 3.0"
|
|
20
|
+
end
|