sax-machine 0.1.0 → 1.3.2

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.
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