sax-machine 0.0.14

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.
@@ -0,0 +1,87 @@
1
+ h1. SAX Machine
2
+
3
+ "http://github.com/pauldix/sax-machine/wikis":http://github.com/pauldix/sax-machine/wikis
4
+
5
+ "http://github.com/pauldix/sax-machine/tree/master":http://github.com/pauldix/sax-machine/tree/master
6
+
7
+ h2. Description
8
+
9
+ A declarative SAX parsing library backed by Nokogiri
10
+
11
+ h2. Usage
12
+
13
+ <pre>
14
+ require 'sax-machine'
15
+
16
+ # Class for parsing an atom entry out of a feedburner atom feed
17
+ class AtomEntry
18
+ include SAXMachine
19
+ element :title
20
+ # the :as argument makes this available through atom_entry.author instead of .name
21
+ element :name, :as => :author
22
+ element "feedburner:origLink", :as => :url
23
+ element :summary
24
+ element :content
25
+ element :published
26
+ end
27
+
28
+ # Class for parsing Atom feeds
29
+ class Atom
30
+ include SAXMachine
31
+ element :title
32
+ # the :with argument means that you only match a link tag that has an attribute of :type => "text/html"
33
+ # the :value argument means that instead of setting the value to the text between the tag,
34
+ # it sets it to the attribute value of :href
35
+ element :link, :value => :href, :as => :url, :with => {:type => "text/html"}
36
+ element :link, :value => :href, :as => :feed_url, :with => {:type => "application/atom+xml"}
37
+ elements :entry, :as => :entries, :class => AtomEntry
38
+ end
39
+
40
+ # you can then parse like this
41
+ feed = Atom.parse(xml_text)
42
+ # then you're ready to rock
43
+ feed.title # => whatever the title of the blog is
44
+ feed.url # => the main url of the blog
45
+ feed.feed_url # => goes to the feedburner feed
46
+
47
+ feed.entries.first.title # => title of the first entry
48
+ feed.entries.first.author # => the author of the first entry
49
+ feed.entries.first.url # => the permalink on the blog for this entry
50
+ # etc ...
51
+
52
+ # you can also use the elements method without specifying a class like so
53
+ class SomeServiceResponse
54
+ elements :message, :as => :messages
55
+ end
56
+
57
+ response = SomeServiceResponse.parse("<response><message>hi</message><message>world</message></response>")
58
+ response.messages.first # => "hi"
59
+ response.messages.last # => "world"
60
+ </pre>
61
+
62
+ h2. LICENSE
63
+
64
+ (The MIT License)
65
+
66
+ Copyright (c) 2009:
67
+
68
+ "Paul Dix":http://pauldix.net
69
+
70
+ Permission is hereby granted, free of charge, to any person obtaining
71
+ a copy of this software and associated documentation files (the
72
+ 'Software'), to deal in the Software without restriction, including
73
+ without limitation the rights to use, copy, modify, merge, publish,
74
+ distribute, sublicense, and/or sell copies of the Software, and to
75
+ permit persons to whom the Software is furnished to do so, subject to
76
+ the following conditions:
77
+
78
+ The above copyright notice and this permission notice shall be
79
+ included in all copies or substantial portions of the Software.
80
+
81
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
82
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
83
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
84
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
85
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
86
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
87
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,14 @@
1
+ require "spec"
2
+ require "spec/rake/spectask"
3
+ require 'lib/sax-machine.rb'
4
+
5
+ Spec::Rake::SpecTask.new do |t|
6
+ t.spec_opts = ['--options', "\"#{File.dirname(__FILE__)}/spec/spec.opts\""]
7
+ t.spec_files = FileList['spec/**/*_spec.rb']
8
+ end
9
+
10
+ task :install do
11
+ rm_rf "*.gem"
12
+ puts `gem build sax-machine.gemspec`
13
+ puts `sudo gem install sax-machine-#{SAXMachine::VERSION}.gem`
14
+ end
@@ -0,0 +1,11 @@
1
+ require "rubygems"
2
+
3
+ $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__))) unless $LOAD_PATH.include?(File.expand_path(File.dirname(__FILE__)))
4
+
5
+ require "sax-machine/sax_document"
6
+ require "sax-machine/sax_handler"
7
+ require "sax-machine/sax_config"
8
+
9
+ module SAXMachine
10
+ VERSION = "0.0.14"
11
+ end
@@ -0,0 +1,33 @@
1
+ module SAXMachine
2
+ class SAXConfig
3
+
4
+ class CollectionConfig
5
+ attr_reader :name
6
+
7
+ def initialize(name, options)
8
+ @name = name.to_s
9
+ @class = options[:class]
10
+ @as = options[:as].to_s
11
+ end
12
+
13
+ def handler
14
+ SAXHandler.new(@class.new)
15
+ end
16
+
17
+ def accessor
18
+ as
19
+ end
20
+
21
+ protected
22
+
23
+ def as
24
+ @as
25
+ end
26
+
27
+ def class
28
+ @class || @name
29
+ end
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,40 @@
1
+ require "sax-machine/sax_element_config"
2
+ require "sax-machine/sax_collection_config"
3
+
4
+ module SAXMachine
5
+ class SAXConfig
6
+ attr_reader :top_level_elements, :collection_elements
7
+
8
+ def initialize
9
+ @top_level_elements = []
10
+ @collection_elements = []
11
+ end
12
+
13
+ def add_top_level_element(name, options)
14
+ @top_level_elements << ElementConfig.new(name, options)
15
+ end
16
+
17
+ def add_collection_element(name, options)
18
+ @collection_elements << CollectionConfig.new(name, options)
19
+ end
20
+
21
+ def collection_config(name)
22
+ @collection_elements.detect { |ce| ce.name.to_s == name.to_s }
23
+ end
24
+
25
+ def element_configs_for_attribute(name, attrs)
26
+ @top_level_elements.select do |element_config|
27
+ element_config.name == name &&
28
+ element_config.has_value_and_attrs_match?(attrs)
29
+ end
30
+ end
31
+
32
+ def element_config_for_tag(name, attrs)
33
+ @top_level_elements.detect do |element_config|
34
+ element_config.name == name &&
35
+ element_config.attrs_match?(attrs)
36
+ end
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,82 @@
1
+ require "nokogiri"
2
+
3
+ module SAXMachine
4
+
5
+ def self.included(base)
6
+ base.extend ClassMethods
7
+ end
8
+
9
+ def parse(xml_text)
10
+ sax_handler = SAXHandler.new(self)
11
+ parser = Nokogiri::XML::SAX::Parser.new(sax_handler)
12
+ parser.parse(xml_text)
13
+ self
14
+ end
15
+
16
+ module ClassMethods
17
+
18
+ def parse(xml_text)
19
+ new.parse(xml_text)
20
+ end
21
+
22
+ def element(name, options = {})
23
+ 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]}=")
31
+ end
32
+
33
+ def columns
34
+ sax_config.top_level_elements
35
+ end
36
+
37
+ def column(sym)
38
+ columns.select{|c| c.column == sym}[0]
39
+ end
40
+
41
+ def data_class(sym)
42
+ column(sym).data_class
43
+ end
44
+
45
+ def required?(sym)
46
+ column(sym).required?
47
+ end
48
+
49
+ 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]}=")
75
+ end
76
+
77
+ def sax_config
78
+ @sax_config ||= SAXConfig.new
79
+ end
80
+ end
81
+
82
+ end
@@ -0,0 +1,65 @@
1
+ module SAXMachine
2
+ class SAXConfig
3
+
4
+ class ElementConfig
5
+ attr_reader :name, :setter, :data_class
6
+
7
+ def initialize(name, options)
8
+ @name = name.to_s
9
+
10
+ if options.has_key?(:with)
11
+ # for faster comparisons later
12
+ @with = options[:with].to_a.flatten.collect {|o| o.to_s}
13
+ else
14
+ @with = nil
15
+ end
16
+
17
+ if options.has_key?(:value)
18
+ @value = options[:value].to_s
19
+ else
20
+ @value = nil
21
+ end
22
+
23
+ @as = options[:as]
24
+ @collection = options[:collection]
25
+
26
+ if @collection
27
+ @setter = "add_#{options[:as]}"
28
+ else
29
+ @setter = "#{@as}="
30
+ end
31
+ @data_class = options[:class]
32
+ @required = options[:required]
33
+ end
34
+
35
+ def column
36
+ @as || @name.to_sym
37
+ end
38
+
39
+ def required?
40
+ @required
41
+ end
42
+
43
+ def value_from_attrs(attrs)
44
+ attrs.index(@value) ? attrs[attrs.index(@value) + 1] : nil
45
+ end
46
+
47
+ def attrs_match?(attrs)
48
+ if @with
49
+ @with == (@with & attrs)
50
+ else
51
+ true
52
+ end
53
+ end
54
+
55
+ def has_value_and_attrs_match?(attrs)
56
+ !@value.nil? && attrs_match?(attrs)
57
+ end
58
+
59
+ def collection?
60
+ @collection
61
+ end
62
+ end
63
+
64
+ end
65
+ end
@@ -0,0 +1,116 @@
1
+ require "nokogiri"
2
+
3
+ module SAXMachine
4
+ class SAXHandler < Nokogiri::XML::SAX::Document
5
+ attr_reader :object
6
+
7
+ def initialize(object)
8
+ @object = object
9
+ @parsed_configs = {}
10
+ end
11
+
12
+ def characters(string)
13
+ if parsing_collection?
14
+ @collection_handler.characters(string)
15
+ elsif @element_config
16
+ @value << string
17
+ end
18
+ end
19
+
20
+ def cdata_block(string)
21
+ characters(string)
22
+ end
23
+
24
+ def start_element(name, attrs = [])
25
+ @name = name
26
+ @attrs = attrs
27
+
28
+ if parsing_collection?
29
+ @collection_handler.start_element(@name, @attrs)
30
+
31
+ elsif @collection_config = sax_config.collection_config(@name)
32
+ @collection_handler = @collection_config.handler
33
+ @collection_handler.start_element(@name, @attrs)
34
+
35
+ elsif (element_configs = sax_config.element_configs_for_attribute(@name, @attrs)).any?
36
+ parse_element_attributes(element_configs)
37
+ set_element_config_for_element_value
38
+
39
+ else
40
+ set_element_config_for_element_value
41
+ end
42
+ end
43
+
44
+ def end_element(name)
45
+ if parsing_collection? && @collection_config.name == name
46
+ @object.send(@collection_config.accessor) << @collection_handler.object
47
+ reset_current_collection
48
+
49
+ elsif parsing_collection?
50
+ @collection_handler.end_element(name)
51
+
52
+ elsif characaters_captured? && !parsed_config?
53
+ mark_as_parsed
54
+ @object.send(@element_config.setter, @value)
55
+ end
56
+
57
+ reset_current_tag
58
+ end
59
+
60
+ def characaters_captured?
61
+ !@value.nil? && !@value.empty?
62
+ end
63
+
64
+ def parsing_collection?
65
+ !@collection_handler.nil?
66
+ end
67
+
68
+ def parse_collection_instance_attributes
69
+ instance = @collection_handler.object
70
+ @attrs.each_with_index do |attr_name,index|
71
+ instance.send("#{attr_name}=", @attrs[index + 1]) if index % 2 == 0 && instance.methods.include?("#{attr_name}=")
72
+ end
73
+ end
74
+
75
+ def parse_element_attributes(element_configs)
76
+ element_configs.each do |ec|
77
+ unless parsed_config?(ec)
78
+ @object.send(ec.setter, ec.value_from_attrs(@attrs))
79
+ mark_as_parsed(ec)
80
+ end
81
+ end
82
+ @element_config = nil
83
+ end
84
+
85
+ def set_element_config_for_element_value
86
+ @value = ""
87
+ @element_config = sax_config.element_config_for_tag(@name, @attrs)
88
+ end
89
+
90
+ def mark_as_parsed(element_config=nil)
91
+ element_config ||= @element_config
92
+ @parsed_configs[element_config] = true unless element_config.collection?
93
+ end
94
+
95
+ def parsed_config?(element_config=nil)
96
+ element_config ||= @element_config
97
+ @parsed_configs[element_config]
98
+ end
99
+
100
+ def reset_current_collection
101
+ @collection_handler = nil
102
+ @collection_config = nil
103
+ end
104
+
105
+ def reset_current_tag
106
+ @name = nil
107
+ @attrs = nil
108
+ @value = nil
109
+ @element_config = nil
110
+ end
111
+
112
+ def sax_config
113
+ @object.class.sax_config
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,388 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe "SAXMachine" do
4
+ describe "element" do
5
+ describe "when parsing a single element" do
6
+ before :each do
7
+ @klass = Class.new do
8
+ include SAXMachine
9
+ element :title
10
+ end
11
+ end
12
+
13
+ it "should provide an accessor" do
14
+ document = @klass.new
15
+ document.title = "Title"
16
+ document.title.should == "Title"
17
+ end
18
+
19
+ it "should allow introspection of the elements" do
20
+ @klass.column_names.should =~ [:title]
21
+ end
22
+
23
+ it "should not overwrite the setter if there is already one present" do
24
+ @klass = Class.new do
25
+ def title=(val)
26
+ @title = "#{val} **"
27
+ end
28
+ include SAXMachine
29
+ element :title
30
+ end
31
+ document = @klass.new
32
+ document.title = "Title"
33
+ document.title.should == "Title **"
34
+ end
35
+ describe "the class attribute" do
36
+ before(:each) do
37
+ @klass = Class.new do
38
+ include SAXMachine
39
+ element :date, :class => DateTime
40
+ end
41
+ @document = @klass.new
42
+ @document.date = DateTime.now.to_s
43
+ end
44
+ it "should be available" do
45
+ @klass.data_class(:date).should == DateTime
46
+ end
47
+ end
48
+ describe "the required attribute" do
49
+ it "should be available" do
50
+ @klass = Class.new do
51
+ include SAXMachine
52
+ element :date, :required => true
53
+ end
54
+ @klass.required?(:date).should be_true
55
+ end
56
+ end
57
+
58
+ it "should not overwrite the accessor when the element is not present" do
59
+ document = @klass.new
60
+ document.title = "Title"
61
+ document.parse("<foo></foo>")
62
+ document.title.should == "Title"
63
+ end
64
+
65
+ it "should overwrite the value when the element is present" do
66
+ document = @klass.new
67
+ document.title = "Old title"
68
+ document.parse("<title>New title</title>")
69
+ document.title.should == "New title"
70
+ end
71
+
72
+ it "should save the element text into an accessor" do
73
+ document = @klass.parse("<title>My Title</title>")
74
+ document.title.should == "My Title"
75
+ end
76
+
77
+ it "should save cdata into an accessor" do
78
+ document = @klass.parse("<title><![CDATA[A Title]]></title>")
79
+ document.title.should == "A Title"
80
+ end
81
+
82
+ it "should save the element text into an accessor when there are multiple elements" do
83
+ document = @klass.parse("<xml><title>My Title</title><foo>bar</foo></xml>")
84
+ document.title.should == "My Title"
85
+ end
86
+
87
+ it "should save the first element text when there are multiple of the same element" do
88
+ document = @klass.parse("<xml><title>My Title</title><title>bar</title></xml>")
89
+ document.title.should == "My Title"
90
+ end
91
+ end
92
+
93
+ describe "when parsing multiple elements" do
94
+ before :each do
95
+ @klass = Class.new do
96
+ include SAXMachine
97
+ element :title
98
+ element :name
99
+ end
100
+ end
101
+
102
+ it "should save the element text for a second tag" do
103
+ document = @klass.parse("<xml><title>My Title</title><name>Paul</name></xml>")
104
+ document.name.should == "Paul"
105
+ document.title.should == "My Title"
106
+ end
107
+ end
108
+
109
+ describe "when using options for parsing elements" do
110
+ describe "using the 'as' option" do
111
+ before :each do
112
+ @klass = Class.new do
113
+ include SAXMachine
114
+ element :description, :as => :summary
115
+ end
116
+ end
117
+
118
+ it "should provide an accessor using the 'as' name" do
119
+ document = @klass.new
120
+ document.summary = "a small summary"
121
+ document.summary.should == "a small summary"
122
+ end
123
+
124
+ it "should save the element text into the 'as' accessor" do
125
+ document = @klass.parse("<description>here is a description</description>")
126
+ document.summary.should == "here is a description"
127
+ end
128
+ end
129
+
130
+ describe "using the :with option" do
131
+ describe "and the :value option" do
132
+ before :each do
133
+ @klass = Class.new do
134
+ include SAXMachine
135
+ element :link, :value => :href, :with => {:foo => "bar"}
136
+ end
137
+ end
138
+
139
+ it "should save the value of a matching element" do
140
+ document = @klass.parse("<link href='test' foo='bar'>asdf</link>")
141
+ document.link.should == "test"
142
+ end
143
+
144
+ it "should save the value of the first matching element" do
145
+ document = @klass.parse("<xml><link href='first' foo='bar' /><link href='second' foo='bar' /></xml>")
146
+ document.link.should == "first"
147
+ end
148
+
149
+ describe "and the :as option" do
150
+ before :each do
151
+ @klass = Class.new do
152
+ include SAXMachine
153
+ element :link, :value => :href, :as => :url, :with => {:foo => "bar"}
154
+ element :link, :value => :href, :as => :second_url, :with => {:asdf => "jkl"}
155
+ end
156
+ end
157
+
158
+ it "should save the value of the first matching element" do
159
+ document = @klass.parse("<xml><link href='first' foo='bar' /><link href='second' asdf='jkl' /><link href='second' foo='bar' /></xml>")
160
+ document.url.should == "first"
161
+ document.second_url.should == "second"
162
+ end
163
+ end
164
+ end
165
+
166
+ describe "with only one element" do
167
+ before :each do
168
+ @klass = Class.new do
169
+ include SAXMachine
170
+ element :link, :with => {:foo => "bar"}
171
+ end
172
+ end
173
+
174
+ it "should save the text of an element that has matching attributes" do
175
+ document = @klass.parse("<link foo=\"bar\">match</link>")
176
+ document.link.should == "match"
177
+ end
178
+
179
+ it "should not save the text of an element that doesn't have matching attributes" do
180
+ document = @klass.parse("<link>no match</link>")
181
+ document.link.should be_nil
182
+ end
183
+
184
+ it "should save the text of an element that has matching attributes when it is the second of that type" do
185
+ document = @klass.parse("<xml><link>no match</link><link foo=\"bar\">match</link></xml>")
186
+ document.link.should == "match"
187
+ end
188
+
189
+ it "should save the text of an element that has matching attributes plus a few more" do
190
+ document = @klass.parse("<xml><link>no match</link><link asdf='jkl' foo='bar'>match</link>")
191
+ document.link.should == "match"
192
+ end
193
+ end
194
+
195
+ describe "with multiple elements of same tag" do
196
+ before :each do
197
+ @klass = Class.new do
198
+ include SAXMachine
199
+ element :link, :as => :first, :with => {:foo => "bar"}
200
+ element :link, :as => :second, :with => {:asdf => "jkl"}
201
+ end
202
+ end
203
+
204
+ it "should match the first element" do
205
+ document = @klass.parse("<xml><link>no match</link><link foo=\"bar\">first match</link><link>no match</link></xml>")
206
+ document.first.should == "first match"
207
+ end
208
+
209
+ it "should match the second element" do
210
+ document = @klass.parse("<xml><link>no match</link><link foo='bar'>first match</link><link asdf='jkl'>second match</link><link>hi</link></xml>")
211
+ document.second.should == "second match"
212
+ end
213
+ end
214
+ end # using the 'with' option
215
+
216
+ describe "using the 'value' option" do
217
+ before :each do
218
+ @klass = Class.new do
219
+ include SAXMachine
220
+ element :link, :value => :foo
221
+ end
222
+ end
223
+
224
+ it "should save the attribute value" do
225
+ document = @klass.parse("<link foo='test'>hello</link>")
226
+ document.link.should == 'test'
227
+ end
228
+
229
+ it "should save the attribute value when there is no text enclosed by the tag" do
230
+ document = @klass.parse("<link foo='test'></link>")
231
+ document.link.should == 'test'
232
+ end
233
+
234
+ it "should save the attribute value when the tag close is in the open" do
235
+ document = @klass.parse("<link foo='test'/>")
236
+ document.link.should == 'test'
237
+ end
238
+
239
+ it "should save two different attribute values on a single tag" do
240
+ @klass = Class.new do
241
+ include SAXMachine
242
+ element :link, :value => :foo, :as => :first
243
+ element :link, :value => :bar, :as => :second
244
+ end
245
+ document = @klass.parse("<link foo='foo value' bar='bar value'></link>")
246
+ document.first.should == "foo value"
247
+ document.second.should == "bar value"
248
+ end
249
+
250
+ it "should not fail if one of the attribute hasn't been defined" do
251
+ @klass = Class.new do
252
+ include SAXMachine
253
+ element :link, :value => :foo, :as => :first
254
+ element :link, :value => :bar, :as => :second
255
+ end
256
+ document = @klass.parse("<link foo='foo value'></link>")
257
+ document.first.should == "foo value"
258
+ document.second.should be_nil
259
+ end
260
+ end
261
+
262
+ describe "when desiring both the content and attributes of an element" do
263
+ before :each do
264
+ @klass = Class.new do
265
+ include SAXMachine
266
+ element :link
267
+ element :link, :value => :foo, :as => :link_foo
268
+ element :link, :value => :bar, :as => :link_bar
269
+ end
270
+ end
271
+
272
+ it "should parse the element and attribute values" do
273
+ document = @klass.parse("<link foo='test1' bar='test2'>hello</link>")
274
+ document.link.should == 'hello'
275
+ document.link_foo.should == 'test1'
276
+ document.link_bar.should == 'test2'
277
+ end
278
+ end
279
+
280
+ end
281
+ end
282
+
283
+ describe "elements" do
284
+ describe "when parsing multiple elements" do
285
+ before :each do
286
+ @klass = Class.new do
287
+ include SAXMachine
288
+ elements :entry, :as => :entries
289
+ end
290
+ end
291
+
292
+ it "should provide a collection accessor" do
293
+ document = @klass.new
294
+ document.entries << :foo
295
+ document.entries.should == [:foo]
296
+ end
297
+
298
+ it "should parse a single element" do
299
+ document = @klass.parse("<entry>hello</entry>")
300
+ document.entries.should == ["hello"]
301
+ end
302
+
303
+ it "should parse multiple elements" do
304
+ document = @klass.parse("<xml><entry>hello</entry><entry>world</entry></xml>")
305
+ document.entries.should == ["hello", "world"]
306
+ end
307
+
308
+ it "should parse multiple elements when taking an attribute value" do
309
+ attribute_klass = Class.new do
310
+ include SAXMachine
311
+ elements :entry, :as => :entries, :value => :foo
312
+ end
313
+ doc = attribute_klass.parse("<xml><entry foo='asdf' /><entry foo='jkl' /></xml>")
314
+ doc.entries.should == ["asdf", "jkl"]
315
+ end
316
+ end
317
+
318
+ describe "when using the class option" do
319
+ before :each do
320
+ class Foo
321
+ include SAXMachine
322
+ element :title
323
+ end
324
+ @klass = Class.new do
325
+ include SAXMachine
326
+ elements :entry, :as => :entries, :class => Foo
327
+ end
328
+ end
329
+
330
+ it "should parse a single element with children" do
331
+ document = @klass.parse("<entry><title>a title</title></entry>")
332
+ document.entries.size.should == 1
333
+ document.entries.first.title.should == "a title"
334
+ end
335
+
336
+ it "should parse multiple elements with children" do
337
+ document = @klass.parse("<xml><entry><title>title 1</title></entry><entry><title>title 2</title></entry></xml>")
338
+ document.entries.size.should == 2
339
+ document.entries.first.title.should == "title 1"
340
+ document.entries.last.title.should == "title 2"
341
+ end
342
+
343
+ it "should not parse a top level element that is specified only in a child" do
344
+ document = @klass.parse("<xml><title>no parse</title><entry><title>correct title</title></entry></xml>")
345
+ document.entries.size.should == 1
346
+ document.entries.first.title.should == "correct title"
347
+ end
348
+
349
+ it "should parse out an attribute value from the tag that starts the collection" do
350
+ class Foo
351
+ element :entry, :value => :href, :as => :url
352
+ end
353
+ document = @klass.parse("<xml><entry href='http://pauldix.net'><title>paul</title></entry></xml>")
354
+ document.entries.size.should == 1
355
+ document.entries.first.title.should == "paul"
356
+ document.entries.first.url.should == "http://pauldix.net"
357
+ end
358
+ end
359
+ end
360
+
361
+ describe "full example" do
362
+ before :each do
363
+ @xml = File.read('spec/sax-machine/atom.xml')
364
+ class AtomEntry
365
+ include SAXMachine
366
+ element :title
367
+ element :name, :as => :author
368
+ element "feedburner:origLink", :as => :url
369
+ element :summary
370
+ element :content
371
+ element :published
372
+ end
373
+
374
+ class Atom
375
+ include SAXMachine
376
+ element :title
377
+ element :link, :value => :href, :as => :url, :with => {:type => "text/html"}
378
+ element :link, :value => :href, :as => :feed_url, :with => {:type => "application/atom+xml"}
379
+ elements :entry, :as => :entries, :class => AtomEntry
380
+ end
381
+ end # before
382
+
383
+ it "should parse the url" do
384
+ f = Atom.parse(@xml)
385
+ f.url.should == "http://www.pauldix.net/"
386
+ end
387
+ end
388
+ end
@@ -0,0 +1,2 @@
1
+ --diff
2
+ --color
@@ -0,0 +1,13 @@
1
+ require "rubygems"
2
+ require "spec"
3
+
4
+ # gem install redgreen for colored test output
5
+ begin require "redgreen" unless ENV['TM_CURRENT_LINE']; rescue LoadError; end
6
+
7
+ path = File.expand_path(File.dirname(__FILE__) + "/../lib/")
8
+ $LOAD_PATH.unshift(path) unless $LOAD_PATH.include?(path)
9
+
10
+ require "lib/sax-machine"
11
+
12
+ # Spec::Runner.configure do |config|
13
+ # end
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sax-machine
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.14
5
+ platform: ruby
6
+ authors:
7
+ - Paul Dix
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-01-13 00:00:00 -05:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: nokogiri
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ description:
26
+ email: paul@pauldix.net
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files: []
32
+
33
+ files:
34
+ - lib/sax-machine.rb
35
+ - lib/sax-machine/sax_config.rb
36
+ - lib/sax-machine/sax_collection_config.rb
37
+ - lib/sax-machine/sax_element_config.rb
38
+ - lib/sax-machine/sax_document.rb
39
+ - lib/sax-machine/sax_handler.rb
40
+ - README.textile
41
+ - Rakefile
42
+ - spec/spec.opts
43
+ - spec/spec_helper.rb
44
+ - spec/sax-machine/sax_document_spec.rb
45
+ has_rdoc: true
46
+ homepage: http://github.com/pauldix/sax-machine
47
+ licenses: []
48
+
49
+ post_install_message:
50
+ rdoc_options: []
51
+
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: "0"
59
+ version:
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: "0"
65
+ version:
66
+ requirements: []
67
+
68
+ rubyforge_project:
69
+ rubygems_version: 1.3.5
70
+ signing_key:
71
+ specification_version: 2
72
+ summary: Declarative SAX Parsing with Nokogiri
73
+ test_files: []
74
+