sax-machine 0.1.0 → 1.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +32 -0
  5. data/Gemfile +13 -2
  6. data/Guardfile +5 -0
  7. data/HISTORY.md +77 -0
  8. data/README.md +207 -0
  9. data/Rakefile +6 -20
  10. data/lib/sax-machine/{sax_ancestor_config.rb → config/sax_ancestor.rb} +3 -7
  11. data/lib/sax-machine/config/sax_attribute.rb +18 -0
  12. data/lib/sax-machine/config/sax_collection.rb +33 -0
  13. data/lib/sax-machine/{sax_element_config.rb → config/sax_element.rb} +23 -31
  14. data/lib/sax-machine/{sax_element_value_config.rb → config/sax_element_value.rb} +7 -8
  15. data/lib/sax-machine/handlers/sax_abstract_handler.rb +199 -0
  16. data/lib/sax-machine/handlers/sax_nokogiri_handler.rb +23 -0
  17. data/lib/sax-machine/handlers/sax_oga_handler.rb +39 -0
  18. data/lib/sax-machine/handlers/sax_ox_handler.rb +56 -0
  19. data/lib/sax-machine/sax_config.rb +13 -9
  20. data/lib/sax-machine/sax_configure.rb +3 -8
  21. data/lib/sax-machine/sax_document.rb +79 -49
  22. data/lib/sax-machine/version.rb +3 -0
  23. data/lib/sax-machine.rb +26 -7
  24. data/sax-machine.gemspec +20 -0
  25. data/spec/fixtures/atom-content.html +15 -0
  26. data/spec/fixtures/atom.xml +165 -0
  27. data/spec/sax-machine/sax_activerecord_spec.rb +21 -0
  28. data/spec/sax-machine/sax_configure_spec.rb +51 -0
  29. data/spec/sax-machine/sax_document_spec.rb +709 -239
  30. data/spec/sax-machine/sax_include_spec.rb +49 -0
  31. data/spec/spec_helper.rb +18 -7
  32. metadata +71 -70
  33. data/README.textile +0 -110
  34. data/lib/sax-machine/sax_attribute_config.rb +0 -40
  35. data/lib/sax-machine/sax_collection_config.rb +0 -45
  36. data/lib/sax-machine/sax_handler.rb +0 -107
  37. data/spec/sax-machine/configure_sax_machine_spec.rb +0 -53
  38. data/spec/sax-machine/include_sax_machine_spec.rb +0 -42
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 18613b2e2d45b525b4557712c55bf590634e3909
4
+ data.tar.gz: 75211d7241bd4e5d1f2eda7ea9ebe6bdad8d3027
5
+ SHA512:
6
+ metadata.gz: 3212a43c353264ca6e00ab2e2e4ec34f04f107dd49506c283af383fa67c9c3c61b56e9a2abbbd3a1e6eae53209336f5647bf6aea822676f2691d5ae4e9c531f3
7
+ data.tar.gz: a778a1df288ce48494bd10300c2170e8ba00666b5a7ea6149d2937d051f003245d633d46962491b9bd0bdbd809f7f8fe8986941b54613230b9c42207a6957bce
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ .idea
2
+ .bundle
3
+ *.gem
4
+ Gemfile.lock
5
+ .rvmrc
6
+ .DS_STORE
7
+ pkg/
8
+ coverage/
9
+ .ruby-version
10
+ .ruby-gemset
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/.travis.yml ADDED
@@ -0,0 +1,32 @@
1
+ language: ruby
2
+
3
+ rvm:
4
+ - 1.9.3
5
+ - 2.0
6
+ - 2.1
7
+ - 2.2
8
+ - jruby-1.7
9
+ - rbx-2
10
+ - ruby-head
11
+ - jruby-head
12
+
13
+ sudo: false
14
+
15
+ env:
16
+ matrix:
17
+ - HANDLER="nokogiri"
18
+ - HANDLER="ox"
19
+ - HANDLER="oga"
20
+
21
+ matrix:
22
+ exclude:
23
+ - env: HANDLER="ox"
24
+ rvm: jruby-1.7
25
+ - env: HANDLER="ox"
26
+ rvm: jruby-head
27
+ allow_failures:
28
+ - env: HANDLER="oga"
29
+ rvm: jruby-1.7
30
+ - rvm: rbx-2
31
+ - rvm: ruby-head
32
+ - rvm: jruby-head
data/Gemfile CHANGED
@@ -1,4 +1,15 @@
1
1
  source "http://rubygems.org"
2
2
 
3
- gem 'nokogiri', '>= 1.4.4'
4
- gem 'rspec', '>= 2.6.0'
3
+ gemspec
4
+
5
+ group :development, :test do
6
+ gem 'rake'
7
+ gem 'guard-rspec'
8
+ gem 'simplecov', require: false, platforms: [:mri]
9
+ gem 'coveralls', require: false, platforms: [:mri]
10
+
11
+ gem 'activerecord', '~> 4.1'
12
+ gem 'nokogiri', '~> 1.6'
13
+ gem 'ox', '>= 2.1.2', platforms: [:mri, :rbx]
14
+ gem 'oga', '>= 0.3.4'
15
+ 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,77 @@
1
+ # HEAD
2
+
3
+ # 1.3.2
4
+
5
+ * Compatibility with Oga 0.3
6
+
7
+ # 1.3.1
8
+
9
+ * Allow default value to be `false` [[#66](https://github.com/pauldix/sax-machine/pull/66)]
10
+ * Support adding class to an attribute [[#68](https://github.com/pauldix/sax-machine/pull/68)]
11
+ * Adjust Ox handler to skip empty text/cdata values
12
+
13
+ # 1.3.0
14
+
15
+ * Improve block modifiers to support all config options
16
+ * Make block modifiers run in instance context
17
+ * Make all handlers support IO as a input
18
+
19
+ # 1.2.0
20
+
21
+ * Add support for blocks as value modifiers [[#61](https://github.com/pauldix/sax-machine/pull/61)]
22
+
23
+ # 1.1.1
24
+
25
+ * Fix Nokogiri autoloading [[#60](https://github.com/pauldix/sax-machine/pull/60)]
26
+
27
+ # 1.1.0
28
+
29
+ * Option to use Oga as a SAX handler
30
+
31
+ # 1.0.3
32
+
33
+ * Remove missed `nokogiri` reference [[#54](https://github.com/pauldix/sax-machine/pull/54)]
34
+ * Add support for `Symbol` data type conversion [[#57](https://github.com/pauldix/sax-machine/pull/57)]
35
+ * Add specs for multiple elements with the same alias [[#53](https://github.com/pauldix/sax-machine/pull/53)]
36
+ * Various code and documentation enhancements
37
+
38
+ # 1.0.2
39
+
40
+ * Make sure SAXConfig getters do not modify internal vars. Prevent race conditions
41
+
42
+ # 1.0.1
43
+
44
+ * Improve normalize_name performance
45
+
46
+ # 1.0.0
47
+
48
+ * Make `nokogiri` dependency optional
49
+ * Add :default argument for elements [[#51](https://github.com/pauldix/sax-machine/pull/51)]
50
+
51
+ # 0.3.0
52
+
53
+ * Option to use Ox as a SAX handler instead of Nokogiri [[#49](https://github.com/pauldix/sax-machine/pull/49)]
54
+ * Bump RSpec to 3.0, convert existing specs
55
+
56
+ # 0.2.1
57
+
58
+ * Turn on replace_entities on Nokogiri parser [[#40](https://github.com/pauldix/sax-machine/pull/40)]
59
+ * Provide mass assignment through initialize method [[#38](https://github.com/pauldix/sax-machine/pull/38)]
60
+ * Bump nokogiri (~> 1.6) and rspec, drop growl dependency
61
+ * Update 'with' option to allow pattern matching in addition to string matching
62
+
63
+ # 0.2.0.rc1
64
+
65
+ * Try to reduce the number of instances of respond_to? in the code by
66
+ pulling common uses of it out to methods. [[#32](https://github.com/pauldix/sax-machine/pull/32)]
67
+ * The parse stack is now composed of simple objects instead of it being
68
+ an array of arrays. [[#32](https://github.com/pauldix/sax-machine/pull/32)]
69
+ * Now using an identifier for an empty buffer instead of empty string. [[#32](https://github.com/pauldix/sax-machine/pull/32)]
70
+ * Clean up several variables that were not being used. [[#32](https://github.com/pauldix/sax-machine/pull/32)]
71
+ * Encapsulate stack so it's not being exposed as part of the API. [[#32](https://github.com/pauldix/sax-machine/pull/32)]
72
+ * `cdata_block` is now an alias instead of delegating to characters. [[#32](https://github.com/pauldix/sax-machine/pull/32)]
73
+
74
+ # 0.1.0
75
+
76
+ * Rename parent to ancestor
77
+ * Add SAXMachine.configure
data/README.md ADDED
@@ -0,0 +1,207 @@
1
+ # SAX Machine
2
+
3
+ ## Status
4
+
5
+ [![Gem Version](https://badge.fury.io/rb/sax-machine.svg)](http://badge.fury.io/rb/sax-machine)
6
+ [![Build Status](https://secure.travis-ci.org/pauldix/sax-machine.svg?branch=master)](http://travis-ci.org/pauldix/sax-machine?branch=master)
7
+ [![Coverage Status](https://img.shields.io/coveralls/pauldix/sax-machine.svg)](https://coveralls.io/r/pauldix/sax-machine?branch=master)
8
+ [![Code Climate](https://img.shields.io/codeclimate/github/pauldix/sax-machine.svg)](https://codeclimate.com/github/pauldix/sax-machine)
9
+ [![Dependencies](https://gemnasium.com/pauldix/sax-machine.svg)](https://gemnasium.com/pauldix/sax-machine)
10
+
11
+ ## Description
12
+
13
+ A declarative SAX parsing library backed by Nokogiri, Ox or Oga.
14
+
15
+ ## Installation
16
+
17
+ Add this line to your application's Gemfile:
18
+
19
+ ```ruby
20
+ gem 'sax-machine'
21
+ ```
22
+
23
+ And then execute:
24
+
25
+ ```bash
26
+ $ bundle
27
+ ```
28
+
29
+ ## Usage
30
+
31
+ SAX Machine can use either `nokogiri`, `ox` or `oga` as XML SAX handler.
32
+
33
+ To use **Nokogiri** add this line to your Gemfile:
34
+
35
+ ```ruby
36
+ gem 'nokogiri', '~> 1.6'
37
+ ```
38
+
39
+ To use **Ox** add this line to your Gemfile:
40
+
41
+ ```ruby
42
+ gem 'ox', '>= 2.1.2'
43
+ ```
44
+
45
+ To use **Oga** add this line to your Gemfile:
46
+
47
+ ```ruby
48
+ gem 'oga', '>= 0.2.0'
49
+ ```
50
+
51
+ You can also specify which handler to use manually, like this:
52
+
53
+ ```ruby
54
+ SAXMachine.handler = :nokogiri
55
+ ```
56
+
57
+ ## Examples
58
+
59
+ Include `SAXMachine` in any class and define properties to parse:
60
+
61
+ ```ruby
62
+ class AtomContent
63
+ include SAXMachine
64
+ attribute :type
65
+ value :text
66
+ end
67
+
68
+ class AtomEntry
69
+ include SAXMachine
70
+ element :title
71
+ # The :as argument makes this available through entry.author instead of .name
72
+ element :name, as: :author
73
+ element "feedburner:origLink", as: :url
74
+ # The :default argument specifies default value for element when it's missing
75
+ element :summary, class: String, default: "No summary available"
76
+ element :content, class: AtomContent
77
+ element :published
78
+ ancestor :ancestor
79
+ end
80
+
81
+ class Atom
82
+ include SAXMachine
83
+ # Use block to modify the returned value
84
+ # Blocks are working with pretty much everything,
85
+ # except for `elements` with `class` attribute
86
+ element :title do |title|
87
+ title.strip
88
+ end
89
+ # The :with argument means that you only match a link tag
90
+ # that has an attribute of type: "text/html"
91
+ element :link, value: :href, as: :url, with: {
92
+ type: "text/html"
93
+ }
94
+ # The :value argument means that instead of setting the value
95
+ # to the text between the tag, it sets it to the attribute value of :href
96
+ element :link, value: :href, as: :feed_url, with: {
97
+ type: "application/atom+xml"
98
+ }
99
+ elements :entry, as: :entries, class: AtomEntry
100
+ end
101
+ ```
102
+
103
+ Then parse any XML with your class:
104
+
105
+ ```ruby
106
+ feed = Atom.parse(xml_text)
107
+
108
+ feed.title # Whatever the title of the blog is
109
+ feed.url # The main URL of the blog
110
+ feed.feed_url # The URL of the blog feed
111
+
112
+ feed.entries.first.title # Title of the first entry
113
+ feed.entries.first.author # The author of the first entry
114
+ feed.entries.first.url # Permalink on the blog for this entry
115
+ feed.entries.first.summary # Returns "No summary available" if summary is missing
116
+ feed.entries.first.ancestor # The Atom ancestor
117
+ feed.entries.first.content # Instance of AtomContent
118
+ feed.entries.first.content.text # Entry content text
119
+ ```
120
+
121
+ You can also use the elements method without specifying a class:
122
+
123
+ ```ruby
124
+ class ServiceResponse
125
+ include SAXMachine
126
+ elements :message, as: :messages
127
+ end
128
+
129
+ response = ServiceResponse.parse("
130
+ <response>
131
+ <message>hi</message>
132
+ <message>world</message>
133
+ </response>
134
+ ")
135
+ response.messages.first # hi
136
+ response.messages.last # world
137
+ ```
138
+
139
+ To limit conflicts in the class used for mappping, you can use the alternate
140
+ `SAXMachine.configure` syntax:
141
+
142
+ ```ruby
143
+ class X < ActiveRecord::Base
144
+ # This way no element, elements or ancestor method will be added to X
145
+ SAXMachine.configure(X) do |c|
146
+ c.element :title
147
+ end
148
+ end
149
+ ```
150
+
151
+ Multiple elements can be mapped to the same alias:
152
+
153
+ ```ruby
154
+ class RSSEntry
155
+ include SAXMachine
156
+ # ...
157
+ element :pubDate, as: :published
158
+ element :pubdate, as: :published
159
+ element :"dc:date", as: :published
160
+ element :"dc:Date", as: :published
161
+ element :"dcterms:created", as: :published
162
+ end
163
+ ```
164
+
165
+ If more than one of these elements exists in the source, the value from the *last one* is used. The order of
166
+ the `element` declarations in the code is unimportant. The order they are encountered while parsing the
167
+ document determines the value assigned to the alias.
168
+
169
+ If an element is defined in the source but is blank (e.g., `<pubDate></pubDate>`), it is ignored, and non-empty one is picked.
170
+
171
+ ## Contributing
172
+
173
+ 1. Fork it
174
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
175
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
176
+ 4. Push to the branch (`git push origin my-new-feature`)
177
+ 5. Create new Pull Request
178
+
179
+ ## LICENSE
180
+
181
+ The MIT License
182
+
183
+ Copyright (c) 2009-2014:
184
+
185
+ * [Paul Dix](http://www.pauldix.net)
186
+ * [Julien Kirch](http://www.archiloque.net)
187
+ * [Ezekiel Templin](http://zeke.templ.in)
188
+ * [Dmitry Krasnoukhov](http://krasnoukhov.com)
189
+
190
+ Permission is hereby granted, free of charge, to any person obtaining
191
+ a copy of this software and associated documentation files (the
192
+ 'Software'), to deal in the Software without restriction, including
193
+ without limitation the rights to use, copy, modify, merge, publish,
194
+ distribute, sublicense, and/or sell copies of the Software, and to
195
+ permit persons to whom the Software is furnished to do so, subject to
196
+ the following conditions:
197
+
198
+ The above copyright notice and this permission notice shall be
199
+ included in all copies or substantial portions of the Software.
200
+
201
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
202
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
203
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
204
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
205
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
206
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
207
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile CHANGED
@@ -1,21 +1,7 @@
1
- require "rspec/core/rake_task"
1
+ #!/usr/bin/env rake
2
+ require 'bundler/gem_tasks'
3
+ require 'rspec/core/rake_task'
2
4
 
3
- $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
4
- require 'sax-machine'
5
-
6
- desc "Run all specs"
7
- RSpec::Core::RakeTask.new do |t|
8
- t.rspec_opts = ['--options', "\"#{File.dirname(__FILE__)}/spec/spec.opts\""]
9
- end
10
-
11
- task :default => [:spec]
12
-
13
- task :test do
14
- sh 'rspec spec'
15
- end
16
-
17
- task :install do
18
- rm_rf "*.gem"
19
- puts `gem build sax-machine.gemspec`
20
- puts `sudo gem install sax-machine-#{SAXMachine::VERSION}.gem`
21
- end
5
+ RSpec::Core::RakeTask.new(:spec)
6
+ task test: :spec
7
+ task default: :test
@@ -1,21 +1,17 @@
1
1
  module SAXMachine
2
2
  class SAXConfig
3
-
4
3
  class AncestorConfig
5
4
  attr_reader :name, :setter
6
5
 
7
6
  def initialize(name, options)
8
- @name = name.to_s
9
-
10
- @as = options[:as]
7
+ @name = name.to_s
8
+ @as = options[:as]
11
9
  @setter = "#{@as}="
12
10
  end
13
11
 
14
12
  def column
15
13
  @as || @name.to_sym
16
14
  end
17
-
18
15
  end
19
-
20
16
  end
21
- end
17
+ end
@@ -0,0 +1,18 @@
1
+ module SAXMachine
2
+ class SAXConfig
3
+ class AttributeConfig < ElementValueConfig
4
+ def value_from_attrs(attrs)
5
+ attrs.fetch(@name, nil)
6
+ end
7
+
8
+ def attrs_match?(attrs)
9
+ attrs.key?(@name) || attrs.value?(@name)
10
+ end
11
+ alias_method :has_value_and_attrs_match?, :attrs_match?
12
+
13
+ def collection?
14
+ false
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,33 @@
1
+ module SAXMachine
2
+ class SAXConfig
3
+ class CollectionConfig
4
+ attr_reader :name
5
+
6
+ def initialize(name, options)
7
+ @name = name.to_s
8
+ @class = options[:class]
9
+ @as = options[:as].to_s
10
+ @with = options.fetch(:with, {})
11
+ end
12
+
13
+ def accessor
14
+ as
15
+ end
16
+
17
+ def attrs_match?(attrs)
18
+ @with.all? do |key, value|
19
+ value === attrs[key.to_s]
20
+ end
21
+ end
22
+
23
+ def data_class
24
+ @class || @name
25
+ end
26
+
27
+ protected
28
+ def as
29
+ @as
30
+ end
31
+ end
32
+ end
33
+ end
@@ -1,37 +1,32 @@
1
1
  module SAXMachine
2
2
  class SAXConfig
3
-
4
3
  class ElementConfig
5
- attr_reader :name, :setter, :data_class, :collection
6
-
4
+ attr_reader :name, :as, :setter, :data_class, :collection, :default
5
+
7
6
  def initialize(name, options)
8
7
  @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
8
+ @with = options.fetch(:with, {})
9
+
10
+ @value = if options.has_key?(:value)
11
+ options[:value].to_s
19
12
  else
20
- @value = nil
13
+ nil
21
14
  end
22
-
15
+
23
16
  @as = options[:as]
24
17
  @collection = options[:collection]
25
-
26
- if @collection
27
- @setter = "add_#{options[:as]}"
18
+ @default = options[:default]
19
+
20
+ @setter = if @collection
21
+ "add_#{options[:as]}"
28
22
  else
29
- @setter = "#{@as}="
23
+ "#{@as}="
30
24
  end
25
+
31
26
  @data_class = options[:class]
32
27
  @required = options[:required]
33
28
  end
34
-
29
+
35
30
  def value_configured?
36
31
  !@value.nil?
37
32
  end
@@ -45,29 +40,26 @@ module SAXMachine
45
40
  end
46
41
 
47
42
  def required?
48
- @required
43
+ !!@required
49
44
  end
50
45
 
51
46
  def value_from_attrs(attrs)
52
- attrs.index(@value) ? attrs[attrs.index(@value) + 1] : nil
47
+ attrs.fetch(@value, nil)
53
48
  end
54
-
49
+
55
50
  def attrs_match?(attrs)
56
- if @with
57
- @with == (@with & attrs)
58
- else
59
- true
51
+ @with.all? do |key, value|
52
+ value === attrs[key.to_s]
60
53
  end
61
54
  end
62
-
55
+
63
56
  def has_value_and_attrs_match?(attrs)
64
57
  !@value.nil? && attrs_match?(attrs)
65
58
  end
66
-
59
+
67
60
  def collection?
68
- @collection
61
+ !!@collection
69
62
  end
70
63
  end
71
-
72
64
  end
73
65
  end
@@ -1,14 +1,14 @@
1
1
  module SAXMachine
2
2
  class SAXConfig
3
-
4
3
  class ElementValueConfig
5
- attr_reader :name, :setter
4
+ attr_reader :name, :setter, :data_class
6
5
 
7
6
  def initialize(name, options)
8
- @name = name.to_s
9
- @as = options[:as]
10
- @setter = "#{@as}="
7
+ @name = name.to_s
8
+ @as = options[:as]
9
+ @setter = "#{@as}="
11
10
  @required = options[:required]
11
+ @data_class = options[:class]
12
12
  end
13
13
 
14
14
  def column
@@ -16,9 +16,8 @@ module SAXMachine
16
16
  end
17
17
 
18
18
  def required?
19
- @required
19
+ !!@required
20
20
  end
21
21
  end
22
-
23
22
  end
24
- end
23
+ end