UnderpantsGnome-sax-machine 0.0.13 → 0.0.14

Sign up to get free protection for your applications and to get access to all the features.
@@ -5,9 +5,9 @@ module SAXMachine
5
5
  attr_reader :name
6
6
 
7
7
  def initialize(name, options)
8
- @name = name.to_s
9
- @class = options[:class]
10
- @as = options[:as].to_s
8
+ @name = name.to_s
9
+ @class = options[:class]
10
+ @as = options[:as].to_s
11
11
  end
12
12
 
13
13
  def handler
@@ -30,4 +30,4 @@ module SAXMachine
30
30
  end
31
31
 
32
32
  end
33
- end
33
+ end
@@ -5,6 +5,7 @@ module SAXMachine
5
5
  class SAXConfig
6
6
  def initialize
7
7
  @top_level_elements = []
8
+ @complex_elements = []
8
9
  @collection_elements = []
9
10
  end
10
11
 
@@ -12,10 +13,18 @@ module SAXMachine
12
13
  @top_level_elements << ElementConfig.new(name, options)
13
14
  end
14
15
 
16
+ def add_complex_element(name, options)
17
+ @complex_elements << ElementConfig.new(name, options)
18
+ end
19
+
15
20
  def add_collection_element(name, options)
16
21
  @collection_elements << CollectionConfig.new(name, options)
17
22
  end
18
23
 
24
+ def complex_config(name)
25
+ @complex_elements.detect { |ce| ce.name.to_s == name.to_s }
26
+ end
27
+
19
28
  def collection_config(name)
20
29
  @collection_elements.detect { |ce| ce.name.to_s == name.to_s }
21
30
  end
@@ -33,6 +42,5 @@ module SAXMachine
33
42
  element_config.attrs_match?(attrs)
34
43
  end
35
44
  end
36
-
37
45
  end
38
- end
46
+ end
@@ -1,37 +1,44 @@
1
1
  require "nokogiri"
2
2
 
3
3
  module SAXMachine
4
-
5
4
  def self.included(base)
6
5
  base.extend ClassMethods
7
6
  end
8
-
7
+
9
8
  def parse(xml_text)
10
9
  sax_handler = SAXHandler.new(self)
11
10
  parser = Nokogiri::XML::SAX::Parser.new(sax_handler)
12
11
  parser.parse(xml_text)
13
12
  self
14
13
  end
15
-
16
- module ClassMethods
17
14
 
15
+ module ClassMethods
18
16
  def parse(xml_text)
19
17
  new.parse(xml_text)
20
18
  end
21
-
19
+
22
20
  def element(name, options = {})
23
21
  options[:as] ||= name
24
- sax_config.add_top_level_element(name, options)
25
-
26
- # we only want to insert the getter and setter if they haven't defined it from elsewhere.
27
- # this is how we allow custom parsing behavior. So you could define the setter
28
- # and have it parse the string into a date or whatever.
29
- attr_reader options[:as] unless instance_methods.include?(options[:as].to_s)
30
- attr_writer options[:as] unless instance_methods.include?("#{options[:as]}=")
22
+
23
+ unless options[:class]
24
+ sax_config.add_top_level_element(name, options)
25
+ else
26
+ sax_config.add_complex_element(name, options)
27
+ end
28
+
29
+ # We only want to insert the getter and setter if they haven't been
30
+ # defined elsewhere. This is how we allow custom parsing behavior. So you
31
+ # could define the setter and have it parse the string into a date or
32
+ # whatever. However, if the getter or setter is defined by a superclass,
33
+ # we go ahead and overwrite it. This allows use to still access elements
34
+ # with names like "id".
35
+ attr_reader options[:as] unless instance_methods(false).include?(options[:as].to_s)
36
+ attr_writer options[:as] unless instance_methods(false).include?("#{options[:as]}=")
31
37
  end
32
-
38
+
33
39
  def elements(name, options = {})
34
40
  options[:as] ||= name
41
+
35
42
  if options[:class]
36
43
  sax_config.add_collection_element(name, options)
37
44
  else
@@ -40,23 +47,23 @@ module SAXMachine
40
47
  #{options[:as]} << value
41
48
  end
42
49
  SRC
50
+
43
51
  sax_config.add_top_level_element(name, options.merge(:collection => true))
44
52
  end
45
-
53
+
46
54
  if !instance_methods.include?(options[:as].to_s)
47
- class_eval <<-SRC
55
+ class_eval <<-SRC
48
56
  def #{options[:as]}
49
57
  @#{options[:as]} ||= []
50
58
  end
51
59
  SRC
52
60
  end
53
-
61
+
54
62
  attr_writer options[:as] unless instance_methods.include?("#{options[:as]}=")
55
63
  end
56
-
64
+
57
65
  def sax_config
58
66
  @sax_config ||= SAXConfig.new
59
67
  end
60
68
  end
61
-
62
- end
69
+ end
@@ -1,28 +1,28 @@
1
1
  module SAXMachine
2
2
  class SAXConfig
3
-
4
3
  class ElementConfig
5
4
  attr_reader :name, :setter
6
-
5
+
7
6
  def initialize(name, options)
8
- @name = name.to_s
9
-
7
+ @name = name.to_s
8
+ @class = options[:class]
9
+
10
10
  if options.has_key?(:with)
11
11
  # for faster comparisons later
12
12
  @with = options[:with].to_a.flatten.collect {|o| o.to_s}
13
13
  else
14
14
  @with = nil
15
15
  end
16
-
16
+
17
17
  if options.has_key?(:value)
18
18
  @value = options[:value].to_s
19
19
  else
20
20
  @value = nil
21
21
  end
22
-
22
+
23
23
  @as = options[:as]
24
24
  @collection = options[:collection]
25
-
25
+
26
26
  if @collection
27
27
  @setter = "add_#{options[:as]}"
28
28
  else
@@ -33,7 +33,7 @@ module SAXMachine
33
33
  def value_from_attrs(attrs)
34
34
  attrs.index(@value) ? attrs[attrs.index(@value) + 1] : nil
35
35
  end
36
-
36
+
37
37
  def attrs_match?(attrs)
38
38
  if @with
39
39
  @with == (@with & attrs)
@@ -41,15 +41,18 @@ module SAXMachine
41
41
  true
42
42
  end
43
43
  end
44
-
44
+
45
45
  def has_value_and_attrs_match?(attrs)
46
46
  !@value.nil? && attrs_match?(attrs)
47
47
  end
48
-
48
+
49
49
  def collection?
50
50
  @collection
51
51
  end
52
+
53
+ def handler
54
+ SAXHandler.new(@class.new) if @class
55
+ end
52
56
  end
53
-
54
57
  end
55
- end
58
+ end
@@ -7,10 +7,13 @@ module SAXMachine
7
7
  def initialize(object)
8
8
  @object = object
9
9
  @parsed_configs = {}
10
+ @parsed_complex_configs = {}
10
11
  end
11
12
 
12
13
  def characters(string)
13
- if parsing_collection?
14
+ if parsing_complex?
15
+ @complex_handler.characters(string)
16
+ elsif parsing_collection?
14
17
  @collection_handler.characters(string)
15
18
  elsif @element_config
16
19
  @value << string
@@ -25,9 +28,16 @@ module SAXMachine
25
28
  @name = name
26
29
  @attrs = attrs
27
30
 
28
- if parsing_collection?
31
+ if parsing_complex?
32
+ @complex_handler.start_element(@name, @attrs)
33
+
34
+ elsif parsing_collection?
29
35
  @collection_handler.start_element(@name, @attrs)
30
36
 
37
+ elsif @complex_config = sax_config.complex_config(@name)
38
+ @complex_handler = @complex_config.handler
39
+ @complex_handler.start_element(@name, @attrs)
40
+
31
41
  elsif @collection_config = sax_config.collection_config(@name)
32
42
  @collection_handler = @collection_config.handler
33
43
  @collection_handler.start_element(@name, @attrs)
@@ -42,7 +52,15 @@ module SAXMachine
42
52
  end
43
53
 
44
54
  def end_element(name)
45
- if parsing_collection? && @collection_config.name == name
55
+ if parsing_complex? && @complex_config.name == name && !parsed_complex_config?
56
+ complex_mark_as_parsed
57
+ @object.send(@complex_config.setter, @complex_handler.object)
58
+ reset_current_complex
59
+
60
+ elsif parsing_complex? && !parsed_complex_config?
61
+ @complex_handler.end_element(name)
62
+
63
+ elsif parsing_collection? && @collection_config.name == name
46
64
  @object.send(@collection_config.accessor) << @collection_handler.object
47
65
  reset_current_collection
48
66
 
@@ -61,6 +79,10 @@ module SAXMachine
61
79
  !@value.nil? && !@value.empty?
62
80
  end
63
81
 
82
+ def parsing_complex?
83
+ !@complex_handler.nil?
84
+ end
85
+
64
86
  def parsing_collection?
65
87
  !@collection_handler.nil?
66
88
  end
@@ -97,15 +119,28 @@ module SAXMachine
97
119
  @parsed_configs[element_config]
98
120
  end
99
121
 
122
+ def complex_mark_as_parsed
123
+ @parsed_complex_configs[@complex_config] = true
124
+ end
125
+
126
+ def parsed_complex_config?
127
+ @parsed_complex_configs[@complex_config]
128
+ end
129
+
100
130
  def reset_current_collection
101
131
  @collection_handler = nil
102
132
  @collection_config = nil
103
133
  end
104
134
 
135
+ def reset_current_complex
136
+ @complex_handler = nil
137
+ @complex_config = nil
138
+ end
139
+
105
140
  def reset_current_tag
106
- @name = nil
107
- @attrs = nil
108
- @value = nil
141
+ @name = nil
142
+ @attrs = nil
143
+ @value = nil
109
144
  @element_config = nil
110
145
  end
111
146
 
@@ -113,4 +148,4 @@ module SAXMachine
113
148
  @object.class.sax_config
114
149
  end
115
150
  end
116
- end
151
+ end
@@ -28,7 +28,7 @@ describe "SAXMachine" do
28
28
  document.title = "Title"
29
29
  document.title.should == "Title **"
30
30
  end
31
-
31
+
32
32
  it "should not overwrite the accessor when the element is not present" do
33
33
  document = @klass.new
34
34
  document.title = "Title"
@@ -47,7 +47,7 @@ describe "SAXMachine" do
47
47
  document = @klass.parse("<title>My Title</title>")
48
48
  document.title.should == "My Title"
49
49
  end
50
-
50
+
51
51
  it "should save cdata into an accessor" do
52
52
  document = @klass.parse("<title><![CDATA[A Title]]></title>")
53
53
  document.title.should == "A Title"
@@ -60,7 +60,7 @@ describe "SAXMachine" do
60
60
 
61
61
  it "should save the first element text when there are multiple of the same element" do
62
62
  document = @klass.parse("<xml><title>My Title</title><title>bar</title></xml>")
63
- document.title.should == "My Title"
63
+ document.title.should == "My Title"
64
64
  end
65
65
  end
66
66
 
@@ -100,7 +100,7 @@ describe "SAXMachine" do
100
100
  document.summary.should == "here is a description"
101
101
  end
102
102
  end
103
-
103
+
104
104
  describe "using the :with option" do
105
105
  describe "and the :value option" do
106
106
  before :each do
@@ -109,17 +109,17 @@ describe "SAXMachine" do
109
109
  element :link, :value => :href, :with => {:foo => "bar"}
110
110
  end
111
111
  end
112
-
112
+
113
113
  it "should save the value of a matching element" do
114
114
  document = @klass.parse("<link href='test' foo='bar'>asdf</link>")
115
115
  document.link.should == "test"
116
116
  end
117
-
117
+
118
118
  it "should save the value of the first matching element" do
119
119
  document = @klass.parse("<xml><link href='first' foo='bar' /><link href='second' foo='bar' /></xml>")
120
120
  document.link.should == "first"
121
121
  end
122
-
122
+
123
123
  describe "and the :as option" do
124
124
  before :each do
125
125
  @klass = Class.new do
@@ -128,15 +128,15 @@ describe "SAXMachine" do
128
128
  element :link, :value => :href, :as => :second_url, :with => {:asdf => "jkl"}
129
129
  end
130
130
  end
131
-
131
+
132
132
  it "should save the value of the first matching element" do
133
133
  document = @klass.parse("<xml><link href='first' foo='bar' /><link href='second' asdf='jkl' /><link href='second' foo='bar' /></xml>")
134
134
  document.url.should == "first"
135
135
  document.second_url.should == "second"
136
- end
136
+ end
137
137
  end
138
138
  end
139
-
139
+
140
140
  describe "with only one element" do
141
141
  before :each do
142
142
  @klass = Class.new do
@@ -157,15 +157,15 @@ describe "SAXMachine" do
157
157
 
158
158
  it "should save the text of an element that has matching attributes when it is the second of that type" do
159
159
  document = @klass.parse("<xml><link>no match</link><link foo=\"bar\">match</link></xml>")
160
- document.link.should == "match"
160
+ document.link.should == "match"
161
161
  end
162
-
162
+
163
163
  it "should save the text of an element that has matching attributes plus a few more" do
164
164
  document = @klass.parse("<xml><link>no match</link><link asdf='jkl' foo='bar'>match</link>")
165
165
  document.link.should == "match"
166
166
  end
167
167
  end
168
-
168
+
169
169
  describe "with multiple elements of same tag" do
170
170
  before :each do
171
171
  @klass = Class.new do
@@ -174,19 +174,19 @@ describe "SAXMachine" do
174
174
  element :link, :as => :second, :with => {:asdf => "jkl"}
175
175
  end
176
176
  end
177
-
177
+
178
178
  it "should match the first element" do
179
179
  document = @klass.parse("<xml><link>no match</link><link foo=\"bar\">first match</link><link>no match</link></xml>")
180
180
  document.first.should == "first match"
181
181
  end
182
-
182
+
183
183
  it "should match the second element" do
184
184
  document = @klass.parse("<xml><link>no match</link><link foo='bar'>first match</link><link asdf='jkl'>second match</link><link>hi</link></xml>")
185
185
  document.second.should == "second match"
186
186
  end
187
187
  end
188
188
  end # using the 'with' option
189
-
189
+
190
190
  describe "using the 'value' option" do
191
191
  before :each do
192
192
  @klass = Class.new do
@@ -194,22 +194,22 @@ describe "SAXMachine" do
194
194
  element :link, :value => :foo
195
195
  end
196
196
  end
197
-
197
+
198
198
  it "should save the attribute value" do
199
199
  document = @klass.parse("<link foo='test'>hello</link>")
200
200
  document.link.should == 'test'
201
201
  end
202
-
202
+
203
203
  it "should save the attribute value when there is no text enclosed by the tag" do
204
204
  document = @klass.parse("<link foo='test'></link>")
205
205
  document.link.should == 'test'
206
206
  end
207
-
207
+
208
208
  it "should save the attribute value when the tag close is in the open" do
209
209
  document = @klass.parse("<link foo='test'/>")
210
210
  document.link.should == 'test'
211
211
  end
212
-
212
+
213
213
  it "should save two different attribute values on a single tag" do
214
214
  @klass = Class.new do
215
215
  include SAXMachine
@@ -220,7 +220,7 @@ describe "SAXMachine" do
220
220
  document.first.should == "foo value"
221
221
  document.second.should == "bar value"
222
222
  end
223
-
223
+
224
224
  it "should not fail if one of the attribute hasn't been defined" do
225
225
  @klass = Class.new do
226
226
  include SAXMachine
@@ -232,7 +232,7 @@ describe "SAXMachine" do
232
232
  document.second.should be_nil
233
233
  end
234
234
  end
235
-
235
+
236
236
  describe "when desiring both the content and attributes of an element" do
237
237
  before :each do
238
238
  @klass = Class.new do
@@ -250,10 +250,47 @@ describe "SAXMachine" do
250
250
  document.link_bar.should == 'test2'
251
251
  end
252
252
  end
253
-
253
+
254
+ end
255
+
256
+ describe "when using the class option" do
257
+ before :each do
258
+ class Foo
259
+ include SAXMachine
260
+ element :title
261
+ end
262
+ @klass = Class.new do
263
+ include SAXMachine
264
+ element :entry, :class => Foo
265
+ end
266
+ end
267
+
268
+ it "should parse a single element with children" do
269
+ document = @klass.parse("<entry><title>a title</title></entry>")
270
+ document.entry.title.should == "a title"
271
+ end
272
+
273
+ it "should use the first element when there are multiple of the same element" do
274
+ document = @klass.parse("<xml><entry><title>title 1</title></entry><entry><title>title 2</title></entry></xml>")
275
+ document.entry.title.should == "title 1"
276
+ end
277
+
278
+ it "should not parse a top level element that is specified only in a child" do
279
+ document = @klass.parse("<xml><title>no parse</title><entry><title>correct title</title></entry></xml>")
280
+ document.entry.title.should == "correct title"
281
+ end
282
+
283
+ it "should parse out an attribute value from the tag that starts the element" do
284
+ class Foo
285
+ element :entry, :value => :href, :as => :url
286
+ end
287
+ document = @klass.parse("<xml><entry href='http://pauldix.net'><title>paul</title></entry></xml>")
288
+ document.entry.title.should == "paul"
289
+ document.entry.url.should == "http://pauldix.net"
290
+ end
254
291
  end
255
292
  end
256
-
293
+
257
294
  describe "elements" do
258
295
  describe "when parsing multiple elements" do
259
296
  before :each do
@@ -262,23 +299,23 @@ describe "SAXMachine" do
262
299
  elements :entry, :as => :entries
263
300
  end
264
301
  end
265
-
302
+
266
303
  it "should provide a collection accessor" do
267
304
  document = @klass.new
268
305
  document.entries << :foo
269
306
  document.entries.should == [:foo]
270
307
  end
271
-
308
+
272
309
  it "should parse a single element" do
273
310
  document = @klass.parse("<entry>hello</entry>")
274
311
  document.entries.should == ["hello"]
275
312
  end
276
-
313
+
277
314
  it "should parse multiple elements" do
278
315
  document = @klass.parse("<xml><entry>hello</entry><entry>world</entry></xml>")
279
316
  document.entries.should == ["hello", "world"]
280
317
  end
281
-
318
+
282
319
  it "should parse multiple elements when taking an attribute value" do
283
320
  attribute_klass = Class.new do
284
321
  include SAXMachine
@@ -288,9 +325,10 @@ describe "SAXMachine" do
288
325
  doc.entries.should == ["asdf", "jkl"]
289
326
  end
290
327
  end
291
-
328
+
292
329
  describe "when using the class option" do
293
330
  before :each do
331
+ Object.send(:remove_const, :Foo)
294
332
  class Foo
295
333
  include SAXMachine
296
334
  element :title
@@ -300,26 +338,26 @@ describe "SAXMachine" do
300
338
  elements :entry, :as => :entries, :class => Foo
301
339
  end
302
340
  end
303
-
341
+
304
342
  it "should parse a single element with children" do
305
343
  document = @klass.parse("<entry><title>a title</title></entry>")
306
344
  document.entries.size.should == 1
307
345
  document.entries.first.title.should == "a title"
308
346
  end
309
-
347
+
310
348
  it "should parse multiple elements with children" do
311
349
  document = @klass.parse("<xml><entry><title>title 1</title></entry><entry><title>title 2</title></entry></xml>")
312
350
  document.entries.size.should == 2
313
351
  document.entries.first.title.should == "title 1"
314
352
  document.entries.last.title.should == "title 2"
315
353
  end
316
-
354
+
317
355
  it "should not parse a top level element that is specified only in a child" do
318
356
  document = @klass.parse("<xml><title>no parse</title><entry><title>correct title</title></entry></xml>")
319
357
  document.entries.size.should == 1
320
358
  document.entries.first.title.should == "correct title"
321
359
  end
322
-
360
+
323
361
  it "should parse out an attribute value from the tag that starts the collection" do
324
362
  class Foo
325
363
  element :entry, :value => :href, :as => :url
@@ -329,9 +367,9 @@ describe "SAXMachine" do
329
367
  document.entries.first.title.should == "paul"
330
368
  document.entries.first.url.should == "http://pauldix.net"
331
369
  end
332
- end
370
+ end
333
371
  end
334
-
372
+
335
373
  describe "full example" do
336
374
  before :each do
337
375
  @xml = File.read('spec/sax-machine/atom.xml')
@@ -344,7 +382,7 @@ describe "SAXMachine" do
344
382
  element :content
345
383
  element :published
346
384
  end
347
-
385
+
348
386
  class Atom
349
387
  include SAXMachine
350
388
  element :title
@@ -353,7 +391,7 @@ describe "SAXMachine" do
353
391
  elements :entry, :as => :entries, :class => AtomEntry
354
392
  end
355
393
  end # before
356
-
394
+
357
395
  it "should parse the url" do
358
396
  f = Atom.parse(@xml)
359
397
  f.url.should == "http://www.pauldix.net/"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: UnderpantsGnome-sax-machine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.13
4
+ version: 0.0.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paul Dix