astro-sax-machine 0.0.12.20090419 → 0.0.14

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,37 +3,38 @@ require "sax-machine/sax_collection_config"
3
3
 
4
4
  module SAXMachine
5
5
  class SAXConfig
6
+ attr_reader :top_level_elements, :collection_elements
7
+
6
8
  def initialize
7
9
  @top_level_elements = []
8
10
  @collection_elements = []
9
11
  end
10
-
12
+
11
13
  def add_top_level_element(name, options)
12
14
  @top_level_elements << ElementConfig.new(name, options)
13
15
  end
14
-
16
+
15
17
  def add_collection_element(name, options)
16
18
  @collection_elements << CollectionConfig.new(name, options)
17
19
  end
18
-
20
+
19
21
  def collection_config(name)
20
22
  @collection_elements.detect { |ce| ce.name.to_s == name.to_s }
21
23
  end
22
24
 
23
- def element_config_for_attribute(name, attrs)
24
- element_configs = @top_level_elements.select do |element_config|
25
+ def element_configs_for_attribute(name, attrs)
26
+ @top_level_elements.select do |element_config|
25
27
  element_config.name == name &&
26
28
  element_config.has_value_and_attrs_match?(attrs)
27
29
  end
28
- element_configs.empty? ? nil : element_configs
29
30
  end
30
-
31
+
31
32
  def element_config_for_tag(name, attrs)
32
33
  @top_level_elements.detect do |element_config|
33
34
  element_config.name == name &&
34
35
  element_config.attrs_match?(attrs)
35
36
  end
36
37
  end
37
-
38
+
38
39
  end
39
40
  end
@@ -41,6 +41,26 @@ module SAXMachine
41
41
  attr_reader options[:as] unless instance_methods.include?(options[:as].to_s)
42
42
  attr_writer options[:as] unless instance_methods.include?("#{options[:as]}=")
43
43
  end
44
+
45
+ def columns
46
+ sax_config.top_level_elements
47
+ end
48
+
49
+ def column(sym)
50
+ columns.select{|c| c.column == sym}[0]
51
+ end
52
+
53
+ def data_class(sym)
54
+ column(sym).data_class
55
+ end
56
+
57
+ def required?(sym)
58
+ column(sym).required?
59
+ end
60
+
61
+ def column_names
62
+ columns.map{|e| e.column}
63
+ end
44
64
 
45
65
  def elements(name, options = {})
46
66
  options[:as] ||= name
@@ -2,7 +2,7 @@ module SAXMachine
2
2
  class SAXConfig
3
3
 
4
4
  class ElementConfig
5
- attr_reader :name, :setter
5
+ attr_reader :name, :setter, :data_class
6
6
 
7
7
  def initialize(name, options)
8
8
  @name = name.to_s
@@ -28,6 +28,16 @@ module SAXMachine
28
28
  else
29
29
  @setter = "#{@as}="
30
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
31
41
  end
32
42
 
33
43
  def value_from_attrs(attrs)
@@ -3,12 +3,12 @@ require "nokogiri"
3
3
  module SAXMachine
4
4
  class SAXHandler < Nokogiri::XML::SAX::Document
5
5
  attr_reader :object
6
-
6
+
7
7
  def initialize(object)
8
8
  @object = object
9
9
  @parsed_configs = {}
10
10
  end
11
-
11
+
12
12
  def characters(string)
13
13
  if parsing_collection?
14
14
  @collection_handler.characters(string)
@@ -16,90 +16,117 @@ module SAXMachine
16
16
  @value << string
17
17
  end
18
18
  end
19
-
19
+
20
20
  def cdata_block(string)
21
21
  characters(string)
22
22
  end
23
-
23
+
24
24
  def start_element(name, attrs = [])
25
- @name = name
26
- @attrs = attrs
27
25
 
26
+ @name = name
27
+ @attrs = attrs.map { |a| SAXHandler.decode_xml(a) }
28
+
28
29
  if parsing_collection?
29
30
  @collection_handler.start_element(@name, @attrs)
30
-
31
+
31
32
  elsif @collection_config = sax_config.collection_config(@name)
32
33
  @collection_handler = @collection_config.handler
33
34
  @collection_handler.start_element(@name, @attrs)
34
-
35
- elsif @element_config = sax_config.element_config_for_attribute(@name, @attrs)
36
- parse_element_attribute
37
-
35
+
36
+ elsif (element_configs = sax_config.element_configs_for_attribute(@name, @attrs)).any?
37
+ parse_element_attributes(element_configs)
38
+ set_element_config_for_element_value
39
+
38
40
  else
39
- @value = ""
40
- @element_config = sax_config.element_config_for_tag(@name, @attrs)
41
+ set_element_config_for_element_value
41
42
  end
42
43
  end
43
-
44
+
44
45
  def end_element(name)
45
46
  if parsing_collection? && @collection_config.name == name
46
47
  @object.send(@collection_config.accessor) << @collection_handler.object
47
48
  reset_current_collection
48
-
49
+
49
50
  elsif parsing_collection?
50
51
  @collection_handler.end_element(name)
51
-
52
- elsif characaters_captured? && !parsed_config?
52
+
53
+ elsif characaters_captured?
53
54
  mark_as_parsed
54
55
  @object.send(@element_config.setter, @value)
55
56
  end
56
-
57
+
57
58
  reset_current_tag
58
59
  end
59
-
60
+
60
61
  def characaters_captured?
61
62
  !@value.nil? && !@value.empty?
62
63
  end
63
-
64
+
64
65
  def parsing_collection?
65
66
  !@collection_handler.nil?
66
67
  end
67
-
68
- def parse_element_attribute
69
- unless parsed_config?
70
- mark_as_parsed
71
- @element_config.each do |config|
72
- @object.send(config.setter, config.value_from_attrs(@attrs))
68
+
69
+ def parse_collection_instance_attributes
70
+ instance = @collection_handler.object
71
+ @attrs.each_with_index do |attr_name,index|
72
+ instance.send("#{attr_name}=", @attrs[index + 1]) if index % 2 == 0 && instance.methods.include?("#{attr_name}=")
73
+ end
74
+ end
75
+
76
+ def parse_element_attributes(element_configs)
77
+ element_configs.each do |ec|
78
+ unless parsed_config?(ec)
79
+ @object.send(ec.setter, ec.value_from_attrs(@attrs))
80
+ mark_as_parsed(ec)
73
81
  end
74
82
  end
75
-
76
83
  @element_config = nil
77
84
  end
78
-
79
- def mark_as_parsed
80
- # TODO: make this code not suck like this
81
- @parsed_configs[@element_config] = true unless (@element_config.respond_to?(:collection?) && @element_config.collection?) ||
82
- (@element_config.class == Array && @element_config.first.collection?)
85
+
86
+ def set_element_config_for_element_value
87
+ @value = ""
88
+ @element_config = sax_config.element_config_for_tag(@name, @attrs)
89
+ end
90
+
91
+ def mark_as_parsed(element_config=nil)
92
+ element_config ||= @element_config
93
+ @parsed_configs[element_config] = true unless element_config.collection?
83
94
  end
84
-
85
- def parsed_config?
86
- @parsed_configs[@element_config]
95
+
96
+ def parsed_config?(element_config=nil)
97
+ element_config ||= @element_config
98
+ @parsed_configs[element_config]
87
99
  end
88
-
100
+
89
101
  def reset_current_collection
90
102
  @collection_handler = nil
91
103
  @collection_config = nil
92
104
  end
93
-
105
+
94
106
  def reset_current_tag
95
107
  @name = nil
96
108
  @attrs = nil
97
109
  @value = nil
98
110
  @element_config = nil
99
111
  end
100
-
112
+
101
113
  def sax_config
102
114
  @object.class.sax_config
103
115
  end
116
+
117
+ ##
118
+ # Decodes XML special characters.
119
+ def self.decode_xml(str)
120
+ return str.map &method(:decode_xml) if str.kind_of?(Array)
121
+
122
+ entities = {
123
+ '#38' => '&amp;',
124
+ '#13' => "\r",
125
+ }
126
+ entities.keys.inject(str) { |string, key|
127
+ string.gsub(/&#{key};/, entities[key])
128
+ }
129
+ end
130
+
104
131
  end
105
132
  end
data/lib/sax-machine.rb CHANGED
@@ -7,5 +7,5 @@ require "sax-machine/sax_handler"
7
7
  require "sax-machine/sax_config"
8
8
 
9
9
  module SAXMachine
10
- VERSION = "0.0.12"
10
+ VERSION = "0.0.14"
11
11
  end
@@ -15,6 +15,10 @@ describe "SAXMachine" do
15
15
  document.title = "Title"
16
16
  document.title.should == "Title"
17
17
  end
18
+
19
+ it "should allow introspection of the elements" do
20
+ @klass.column_names.should =~ [:title]
21
+ end
18
22
 
19
23
  it "should not overwrite the setter if there is already one present" do
20
24
  @klass = Class.new do
@@ -28,6 +32,28 @@ describe "SAXMachine" do
28
32
  document.title = "Title"
29
33
  document.title.should == "Title **"
30
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
31
57
 
32
58
  it "should not overwrite the accessor when the element is not present" do
33
59
  document = @klass.new
@@ -110,6 +136,11 @@ describe "SAXMachine" do
110
136
  end
111
137
  end
112
138
 
139
+ it "should escape correctly the ampersand" do
140
+ document = @klass.parse("<link href='http://api.flickr.com/services/feeds/photos_public.gne?id=49724566@N00&amp;lang=en-us&amp;format=atom' foo='bar'>asdf</link>")
141
+ document.link.should == "http://api.flickr.com/services/feeds/photos_public.gne?id=49724566@N00&amp;lang=en-us&amp;format=atom"
142
+ end
143
+
113
144
  it "should save the value of a matching element" do
114
145
  document = @klass.parse("<link href='test' foo='bar'>asdf</link>")
115
146
  document.link.should == "test"
@@ -232,6 +263,25 @@ describe "SAXMachine" do
232
263
  document.second.should be_nil
233
264
  end
234
265
  end
266
+
267
+ describe "when desiring both the content and attributes of an element" do
268
+ before :each do
269
+ @klass = Class.new do
270
+ include SAXMachine
271
+ element :link
272
+ element :link, :value => :foo, :as => :link_foo
273
+ element :link, :value => :bar, :as => :link_bar
274
+ end
275
+ end
276
+
277
+ it "should parse the element and attribute values" do
278
+ document = @klass.parse("<link foo='test1' bar='test2'>hello</link>")
279
+ document.link.should == 'hello'
280
+ document.link_foo.should == 'test1'
281
+ document.link_bar.should == 'test2'
282
+ end
283
+ end
284
+
235
285
  end
236
286
  end
237
287
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: astro-sax-machine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.12.20090419
4
+ version: 0.0.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paul Dix
@@ -44,6 +44,7 @@ files:
44
44
  - spec/sax-machine/sax_document_spec.rb
45
45
  has_rdoc: false
46
46
  homepage: http://github.com/pauldix/sax-machine
47
+ licenses:
47
48
  post_install_message:
48
49
  rdoc_options: []
49
50
 
@@ -64,7 +65,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
64
65
  requirements: []
65
66
 
66
67
  rubyforge_project:
67
- rubygems_version: 1.2.0
68
+ rubygems_version: 1.3.5
68
69
  signing_key:
69
70
  specification_version: 2
70
71
  summary: Declarative SAX Parsing with Nokogiri