astro-sax-machine 0.0.12.20090419 → 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.
@@ -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