sax-machine 0.0.16 → 0.2.0.rc1

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.
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ .idea
2
+ .bundle
3
+ *.gem
4
+ Gemfile.lock
5
+ .rvmrc
6
+ .DS_STORE
7
+ pkg/
8
+ coverage/
data/.rspec CHANGED
@@ -1,2 +1,2 @@
1
- --require=./spec/spec_helper.rb
2
1
  --color
2
+ --format progress
data/.travis.yml ADDED
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.2
5
+ - 1.9.3
6
+ - rbx-18mode
7
+ - rbx-19mode
8
+ notifications:
9
+ irc: "irc.freenode.org#sax-machine"
data/Gemfile CHANGED
@@ -1,4 +1,10 @@
1
1
  source "http://rubygems.org"
2
2
 
3
- gem 'nokogiri', '>= 1.4.4'
4
- gem 'rspec', '>= 2.4.0'
3
+ gemspec
4
+
5
+ group :development, :test do
6
+ gem 'rake'
7
+ gem 'guard-rspec'
8
+ gem 'growl', :require => false
9
+ gem 'simplecov', :require => false, :platforms => :mri_19
10
+ end
data/Guardfile ADDED
@@ -0,0 +1,5 @@
1
+ guard 'rspec', :version => 2 do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { "spec" }
5
+ end
data/HISTORY.md ADDED
@@ -0,0 +1,13 @@
1
+ # 0.2.0.rc1
2
+ * Tried to reduce the number of instances of respond_to? in the code by
3
+ pulling common uses of it out to methods. [[#32](https://github.com/pauldix/sax-machine/pull/32)]
4
+ * The parse stack is now composed of simple objects instead of it being
5
+ an array of arrays. [[#32](https://github.com/pauldix/sax-machine/pull/32)]
6
+ * Now using an identifier for an empty buffer instead of empty string. [[#32](https://github.com/pauldix/sax-machine/pull/32)]
7
+ * Cleaned up several variables that were not being used. [[#32](https://github.com/pauldix/sax-machine/pull/32)]
8
+ * Encapsulated stack so it's not being exposed as part of the API. [[#32](https://github.com/pauldix/sax-machine/pull/32)]
9
+ * #cdata_block is now an alias instead of delegating to characters. [[#32](https://github.com/pauldix/sax-machine/pull/32)]
10
+
11
+ # 0.1.0
12
+ * rename parent to ancestor
13
+ * added SAXMachine.configure
@@ -1,18 +1,24 @@
1
- h1. SAX Machine
1
+ # SAX Machine [![Build Status](https://secure.travis-ci.org/pauldix/sax-machine.png?branch=master)](http://travis-ci.org/pauldix/sax-machine)
2
2
 
3
- "http://github.com/pauldix/sax-machine/wikis":http://github.com/pauldix/sax-machine/wikis
3
+ [Wiki](https://github.com/pauldix/sax-machine/wiki)
4
4
 
5
- "http://github.com/pauldix/sax-machine/tree/master":http://github.com/pauldix/sax-machine/tree/master
6
-
7
- h2. Description
5
+ ## Description
8
6
 
9
7
  A declarative SAX parsing library backed by Nokogiri
10
8
 
11
- h2. Usage
12
-
13
- <pre>
9
+ ## Usage
10
+ ```ruby
14
11
  require 'sax-machine'
15
12
 
13
+ # Class for information associated with content parts in a feed.
14
+ # Ex: <content type="text">sample</content>
15
+ # instance.type will be "text", instance.text will be "sample"
16
+ class AtomContent
17
+ include SAXMachine
18
+ attribute :type
19
+ value :text
20
+ end
21
+
16
22
  # Class for parsing an atom entry out of a feedburner atom feed
17
23
  class AtomEntry
18
24
  include SAXMachine
@@ -21,8 +27,9 @@ class AtomEntry
21
27
  element :name, :as => :author
22
28
  element "feedburner:origLink", :as => :url
23
29
  element :summary
24
- element :content
30
+ element :content, :class => AtomContent
25
31
  element :published
32
+ ancestor :ancestor
26
33
  end
27
34
 
28
35
  # Class for parsing Atom feeds
@@ -43,30 +50,43 @@ feed = Atom.parse(xml_text)
43
50
  feed.title # => whatever the title of the blog is
44
51
  feed.url # => the main url of the blog
45
52
  feed.feed_url # => goes to the feedburner feed
46
-
53
+
47
54
  feed.entries.first.title # => title of the first entry
48
55
  feed.entries.first.author # => the author of the first entry
49
56
  feed.entries.first.url # => the permalink on the blog for this entry
57
+ feed.entries.first.ancestor # => the Atom ancestor
50
58
  # etc ...
51
59
 
52
60
  # you can also use the elements method without specifying a class like so
53
61
  class SomeServiceResponse
62
+ include SAXMachine
54
63
  elements :message, :as => :messages
55
64
  end
56
65
 
57
66
  response = SomeServiceResponse.parse("<response><message>hi</message><message>world</message></response>")
58
67
  response.messages.first # => "hi"
59
68
  response.messages.last # => "world"
60
- </pre>
61
69
 
62
- h2. LICENSE
70
+ # To limit conflicts in the class used for mappping, you can use the alternate SAXMachine.configure syntax
71
+
72
+ class X < ActiveRecord::Base
73
+ # this way no element, elements or ancestor method will be added to X
74
+ SAXMachine.configure(X) do |c|
75
+ c.element :title
76
+ end
77
+ end
78
+ ```
79
+
80
+ ## LICENSE
81
+
82
+ The MIT License
83
+
84
+ Copyright (c) 2009-2012:
85
+
86
+ * [Paul Dix](http://www.pauldix.net)
87
+ * [Julien Kirch](http://www.archiloque.net)
88
+ * [Ezekiel Templin](http://zeke.templ.in)
63
89
 
64
- (The MIT License)
65
-
66
- Copyright (c) 2009:
67
-
68
- "Paul Dix":http://pauldix.net
69
-
70
90
  Permission is hereby granted, free of charge, to any person obtaining
71
91
  a copy of this software and associated documentation files (the
72
92
  'Software'), to deal in the Software without restriction, including
@@ -74,10 +94,10 @@ without limitation the rights to use, copy, modify, merge, publish,
74
94
  distribute, sublicense, and/or sell copies of the Software, and to
75
95
  permit persons to whom the Software is furnished to do so, subject to
76
96
  the following conditions:
77
-
97
+
78
98
  The above copyright notice and this permission notice shall be
79
99
  included in all copies or substantial portions of the Software.
80
-
100
+
81
101
  THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
82
102
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
83
103
  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
data/Rakefile CHANGED
@@ -1,19 +1,6 @@
1
- require "rspec/core/rake_task"
2
- require 'lib/sax-machine.rb'
3
-
4
- desc "Run all specs"
5
- RSpec::Core::RakeTask.new do |t|
6
- t.rspec_opts = ['--options', "\"#{File.dirname(__FILE__)}/spec/spec.opts\""]
7
- end
8
-
9
- task :default => [:spec]
10
-
11
- task :test do
12
- sh 'rspec spec'
13
- end
14
-
15
- task :install do
16
- rm_rf "*.gem"
17
- puts `gem build sax-machine.gemspec`
18
- puts `sudo gem install sax-machine-#{SAXMachine::VERSION}.gem`
19
- end
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require 'rspec/core/rake_task'
4
+ RSpec::Core::RakeTask.new(:spec)
5
+ task :test => :spec
6
+ task :default => :test
@@ -0,0 +1,21 @@
1
+ module SAXMachine
2
+ class SAXConfig
3
+
4
+ class AncestorConfig
5
+ attr_reader :name, :setter
6
+
7
+ def initialize(name, options)
8
+ @name = name.to_s
9
+
10
+ @as = options[:as]
11
+ @setter = "#{@as}="
12
+ end
13
+
14
+ def column
15
+ @as || @name.to_sym
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,40 @@
1
+ module SAXMachine
2
+ class SAXConfig
3
+
4
+ class AttributeConfig
5
+ attr_reader :name, :setter
6
+
7
+ def initialize(name, options)
8
+ @name = name.to_s
9
+ @as = options[:as]
10
+ @setter = "#{@as}="
11
+ @required = options[:required]
12
+ end
13
+
14
+ def column
15
+ @as || @name.to_sym
16
+ end
17
+
18
+ def required?
19
+ @required
20
+ end
21
+
22
+ def value_from_attrs(attrs)
23
+ attrs.index(@name) ? attrs[attrs.index(@name) + 1] : nil
24
+ end
25
+
26
+ def attrs_match?(attrs)
27
+ attrs.index(@name) ? true : false
28
+ end
29
+
30
+ def has_value_and_attrs_match?(attrs)
31
+ attrs_match?(attrs)
32
+ end
33
+
34
+ def collection?
35
+ false
36
+ end
37
+ end
38
+
39
+ end
40
+ end
@@ -1,47 +1,74 @@
1
+ require "sax-machine/sax_attribute_config"
2
+ require "sax-machine/sax_element_value_config"
1
3
  require "sax-machine/sax_element_config"
2
4
  require "sax-machine/sax_collection_config"
5
+ require "sax-machine/sax_ancestor_config"
3
6
 
4
7
  module SAXMachine
5
8
  class SAXConfig
6
- attr_accessor :top_level_elements, :collection_elements
7
-
9
+
10
+ attr_accessor :top_level_elements, :top_level_attributes, :top_level_element_value, :collection_elements, :ancestors
11
+
8
12
  def initialize
9
- @top_level_elements = {}
10
- @collection_elements = {}
13
+ # Default value is an empty array
14
+ @top_level_elements = Hash.new { |hash, key| hash[key] = [] }
15
+ @top_level_attributes = []
16
+ @top_level_element_value = []
17
+ @collection_elements = Hash.new { |hash, key| hash[key] = [] }
18
+ @ancestors = []
11
19
  end
12
-
20
+
13
21
  def columns
14
22
  @top_level_elements.map {|name, ecs| ecs }.flatten
15
23
  end
16
-
24
+
17
25
  def initialize_copy(sax_config)
26
+ super
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
- ces = @collection_elements[name.to_s]
34
- ces && ces.detect { |cc| cc.attrs_match?(attrs) }
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
- tes = @top_level_elements[name.to_s]
39
- tes && tes.select { |ec| ec.has_value_and_attrs_match?(attrs) } || []
67
+ @top_level_elements[name.to_s].select { |ec| ec.has_value_and_attrs_match?(attrs) }
40
68
  end
41
69
 
42
70
  def element_config_for_tag(name, attrs)
43
- tes = @top_level_elements[name.to_s]
44
- tes && tes.detect { |ec| ec.attrs_match?(attrs) }
71
+ @top_level_elements[name.to_s].detect { |ec| ec.attrs_match?(attrs) }
45
72
  end
46
73
  end
47
74
  end
@@ -0,0 +1,38 @@
1
+ module SAXMachine
2
+
3
+ def self.configure(clazz)
4
+ extended_clazz = Class.new(clazz)
5
+ extended_clazz.send(:include, SAXMachine)
6
+
7
+ # override create_attr to create attributes on the original class
8
+ def extended_clazz.create_attr real_name
9
+ superclass.send(:attr_reader, real_name) unless superclass.method_defined?(real_name)
10
+ superclass.send(:attr_writer, real_name) unless superclass.method_defined?("#{real_name}=")
11
+ end
12
+
13
+ yield(extended_clazz)
14
+
15
+ clazz.extend LightWeightSaxMachine
16
+ clazz.sax_config = extended_clazz.sax_config
17
+
18
+ (class << clazz;self;end).send(:define_method, :parse) do |xml_text|
19
+ extended_clazz.parse(xml_text)
20
+ end
21
+
22
+ end
23
+
24
+ module LightWeightSaxMachine
25
+
26
+ attr_writer :sax_config
27
+
28
+ def sax_config
29
+ @sax_config ||= SAXConfig.new
30
+ end
31
+
32
+ def inherited(subclass)
33
+ subclass.sax_config.send(:initialize_copy, self.sax_config)
34
+ end
35
+
36
+ end
37
+
38
+ end
@@ -1,33 +1,50 @@
1
1
  require "nokogiri"
2
2
 
3
3
  module SAXMachine
4
-
4
+
5
5
  def self.included(base)
6
6
  base.extend ClassMethods
7
7
  end
8
-
9
- def parse(xml_text)
10
- sax_handler = SAXHandler.new(self)
8
+
9
+ def parse(xml_text, on_error = nil, on_warning = nil)
10
+ sax_handler = SAXHandler.new(self, on_error, on_warning)
11
11
  parser = Nokogiri::XML::SAX::Parser.new(sax_handler)
12
12
  parser.parse(xml_text)
13
13
  self
14
14
  end
15
-
15
+
16
16
  module ClassMethods
17
17
 
18
- def parse(xml_text)
19
- new.parse(xml_text)
18
+ def inherited(subclass)
19
+ subclass.sax_config.send(:initialize_copy, self.sax_config)
20
20
  end
21
-
21
+
22
+ def parse(xml_text, on_error = nil, on_warning = nil)
23
+ new.parse(xml_text, on_error, on_warning)
24
+ end
25
+
22
26
  def element(name, options = {})
23
- options[:as] ||= name
27
+ real_name = (options[:as] ||= name).to_s
24
28
  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]}=")
29
+ create_attr real_name
30
+ end
31
+
32
+ def attribute(name, options = {})
33
+ real_name = (options[:as] ||= name).to_s
34
+ sax_config.add_top_level_attribute(self.class.to_s, options.merge(:name => name))
35
+ create_attr real_name
36
+ end
37
+
38
+ def value(name, options = {})
39
+ real_name = (options[:as] ||= name).to_s
40
+ sax_config.add_top_level_element_value(self.class.to_s, options.merge(:name => name))
41
+ create_attr real_name
42
+ end
43
+
44
+ def ancestor(name, options = {})
45
+ real_name = (options[:as] ||= name).to_s
46
+ sax_config.add_ancestor(name, options)
47
+ create_attr(real_name)
31
48
  end
32
49
 
33
50
  def columns
@@ -35,7 +52,7 @@ module SAXMachine
35
52
  end
36
53
 
37
54
  def column(sym)
38
- columns.select{|c| c.column == sym}[0]
55
+ columns.select { |c| c.column == sym }[0]
39
56
  end
40
57
 
41
58
  def data_class(sym)
@@ -47,9 +64,9 @@ module SAXMachine
47
64
  end
48
65
 
49
66
  def column_names
50
- columns.map{|e| e.column}
67
+ columns.map { |e| e.column }
51
68
  end
52
-
69
+
53
70
  def elements(name, options = {})
54
71
  options[:as] ||= name
55
72
  if options[:class]
@@ -62,21 +79,29 @@ module SAXMachine
62
79
  SRC
63
80
  sax_config.add_top_level_element(name, options.merge(:collection => true))
64
81
  end
65
-
66
- if !instance_methods.include?(options[:as].to_s)
67
- class_eval <<-SRC
82
+
83
+ if !method_defined?(options[:as].to_s)
84
+ class_eval <<-SRC
68
85
  def #{options[:as]}
69
86
  @#{options[:as]} ||= []
70
87
  end
71
88
  SRC
72
89
  end
73
-
74
- attr_writer options[:as] unless instance_methods.include?("#{options[:as]}=")
90
+
91
+ attr_writer options[:as] unless method_defined?("#{options[:as]}=")
75
92
  end
76
-
93
+
77
94
  def sax_config
78
95
  @sax_config ||= SAXConfig.new
79
96
  end
97
+
98
+ # we only want to insert the getter and setter if they haven't defined it from elsewhere.
99
+ # this is how we allow custom parsing behavior. So you could define the setter
100
+ # and have it parse the string into a date or whatever.
101
+ def create_attr real_name
102
+ attr_reader real_name unless method_defined?(real_name)
103
+ attr_writer real_name unless method_defined?("#{real_name}=")
104
+ end
80
105
  end
81
-
82
- end
106
+
107
+ end
@@ -31,6 +31,14 @@ module SAXMachine
31
31
  @data_class = options[:class]
32
32
  @required = options[:required]
33
33
  end
34
+
35
+ def value_configured?
36
+ !@value.nil?
37
+ end
38
+
39
+ def to_s
40
+ "name: #{@name} dataclass: #{@data_class} setter: #{@setter} required: #{@required} value: #{@value} as:#{@as} collection: #{@collection} with: #{@with}"
41
+ end
34
42
 
35
43
  def column
36
44
  @as || @name.to_sym
@@ -62,4 +70,4 @@ module SAXMachine
62
70
  end
63
71
 
64
72
  end
65
- end
73
+ end
@@ -0,0 +1,24 @@
1
+ module SAXMachine
2
+ class SAXConfig
3
+
4
+ class ElementValueConfig
5
+ attr_reader :name, :setter
6
+
7
+ def initialize(name, options)
8
+ @name = name.to_s
9
+ @as = options[:as]
10
+ @setter = "#{@as}="
11
+ @required = options[:required]
12
+ end
13
+
14
+ def column
15
+ @as || @name.to_sym
16
+ end
17
+
18
+ def required?
19
+ @required
20
+ end
21
+ end
22
+
23
+ end
24
+ end