saxy 0.2.3 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 923e7d1c218d9e304ee18fb87f6f2a1a9d06d384
4
- data.tar.gz: 79b769b7699aee8503635fdfdc10b09afb9665a2
3
+ metadata.gz: 52a8b74342fe631ae2787bd38fedcb3600b24730
4
+ data.tar.gz: 37ef8a7e143494b48ba13c3d7470c84e459d0bf7
5
5
  SHA512:
6
- metadata.gz: edc331020d4c82df9b1d84656699b1060a6a9ee2324032a4a792208b1a991dadfc23c1668db1f2719c30afe967dd2f57f06bf2eafc61e410808a62bcbef44f8d
7
- data.tar.gz: e140b6138c76e6fa602f4ce4a5a284b0b0213235057ea78da5202871bd85fce1e0df0441010eea5b1aee915c188b8520885a66fa9aa518c595e65a5d61ee502d
6
+ metadata.gz: 468d1551a3fdafc0c0b37cdffb5482f1ecd5cf2fac2c8f63b23c7d75dc38c1d13b47ac8a7d195b8031251d62a5ea13492674cf418f25cf732d30e073a8f99f9b
7
+ data.tar.gz: d026bfaa8643b1eeb12ce00eecc13e4f1d27e531308706e14d05f1a9bc2d22755a5e2479482d5024ce298732373ea62498314d083fe4a2fa2aa8e5da3399c6a9
data/.gitignore CHANGED
@@ -3,7 +3,7 @@
3
3
  .bundle
4
4
  .config
5
5
  .yardoc
6
- Gemfile.lock
6
+ *emfile.lock
7
7
  InstalledFiles
8
8
  _yardoc
9
9
  coverage
data/.travis.yml CHANGED
@@ -1,14 +1,20 @@
1
1
  script: "bundle exec rspec ./spec/"
2
+ sudo: false
2
3
  language: ruby
3
4
  rvm:
5
+ - 2.5
6
+ - 2.4
7
+ - 2.3
8
+ - 2.2
9
+ - 2.1
4
10
  - 2.0.0
5
11
  - 1.9.3
6
- - 1.9.2
12
+ gemfile:
13
+ - gemfiles/nokogiri_1.6.gemfile
14
+ - gemfiles/nokogiri_latest.gemfile
7
15
  matrix:
8
- include:
9
- - rvm: 1.8.7
10
- gemfile: gemfiles/1.8
11
- - rvm: ree
12
- gemfile: gemfiles/1.8
13
-
14
-
16
+ exclude:
17
+ - rvm: 2.0.0
18
+ gemfile: gemfiles/nokogiri_latest.gemfile
19
+ - rvm: 1.9.3
20
+ gemfile: gemfiles/nokogiri_latest.gemfile
data/Appraisals ADDED
@@ -0,0 +1,7 @@
1
+ appraise "nokogiri 1.6" do
2
+ gem "nokogiri", "~> 1.6.0"
3
+ end
4
+
5
+ appraise "nokogiri latest" do
6
+ gem "nokogiri"
7
+ end
data/CHANGELOG.md ADDED
@@ -0,0 +1,35 @@
1
+ # Saxy Changelog
2
+
3
+ ## 0.7.1
4
+
5
+ * Fixed whitespace stripping from elements' contents, now only leading and trailing whitespace is stripped (reported by @wzcolon, thanks!).
6
+ * Added `error_handler` option to make it possible to handle errors on-the-fly instead of raising them.
7
+
8
+ ## 0.7.0
9
+
10
+ * [BREAKING] Yielded hashes now have strings as keys instead of symbols (performance and security fix).
11
+
12
+ ## 0.6.1
13
+
14
+ * Fixed passing options from `Saxy.parse` to parser's initializer
15
+
16
+ ## 0.6.0
17
+
18
+ * [BREAKING] `Saxy::ParsingError` now inherits from `StandardError`, not `Exception`.
19
+ * [BREAKING] Forced encoding is now an option instead of third argument of `Saxy.parse` method.
20
+ * Added `recovery` and `replace_entities` options that are internally passed to `Nokogiri::XML::SAX::ParserContext`
21
+ * Added `context` method to `Saxy::ParsingError` that holds parser context at the time of error.
22
+
23
+ ## 0.5.2
24
+
25
+ * Added optional `encoding` argument to `Saxy.parse`
26
+
27
+ ## 0.5.1
28
+
29
+ * Removed `activesupport` dependency
30
+
31
+ ## 0.5.0
32
+
33
+ * [BREAKING] Dropped support for ruby 1.9.2 and lower
34
+ * [BREAKING] Yields hashes instead of `OpenStruct`s
35
+ * Added support for `IO`-like objects
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2012-2016 Humante LLC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,10 +1,13 @@
1
1
  # Saxy
2
2
 
3
- [![Build Status](https://secure.travis-ci.org/monterail/saxy.png)](http://travis-ci.org/monterail/saxy)
3
+ [![Gem Version](https://badge.fury.io/rb/saxy.svg)](https://badge.fury.io/rb/saxy)
4
+ [![Build Status](https://api.travis-ci.org/humante/saxy.svg)](http://travis-ci.org/humante/saxy)
4
5
 
5
- Memory-efficient XML parser. Finds object definitions in XML and translates them into Ruby objects.
6
+ Memory-efficient XML parser. Finds object definitions in XML and translates them into Ruby hashes.
6
7
 
7
- It uses SAX parser under the hood, which means that it doesn't load the whole XML file into memory. It goes once though it and yields objects along the way.
8
+ It uses SAX parser (provided by Nokogiri gem) under the hood, which means that it doesn't load the whole XML file into memory. It goes once through it and yields hashes along the way.
9
+
10
+ In result the memory footprint of the parser remains small and more or less constant irrespective of the size of the XML file, be it few KB or hundreds of GB.
8
11
 
9
12
  ## Installation
10
13
 
@@ -20,63 +23,146 @@ Or install it yourself as:
20
23
 
21
24
  $ gem install saxy
22
25
 
26
+ ## Requirements
27
+
28
+ As of `0.5.0` version `saxy` requires ruby 1.9.3 or higher. Previous versions of the gem work with ruby 1.8 and 1.9.2 (see below), but they are not maintained anymore.
29
+
30
+ ### Ruby 1.8 support
31
+
32
+ See `ruby-1.8` branch. Install with:
33
+
34
+ gem 'saxy', '~> 0.3.0'
35
+
36
+ ### Ruby 1.9.2 support
37
+
38
+ See `ruby-1.9.2` branch. Install with:
39
+
40
+ gem 'saxy', '~> 0.4.0'
41
+
42
+ ## Changelog
43
+
44
+ See `CHANGELOG.md` file.
45
+
23
46
  ## Usage
24
47
 
25
- Assume the XML file:
26
-
27
- <?xml version='1.0' encoding='UTF-8'?>
28
- <webstore>
29
- <name>Amazon</name>
30
- <products>
31
- <product>
32
- <name>Kindle - The world's best-selling e-reader.</name>
33
- <images>
34
- <thumbSize width="80" height="60">http://amazon.com/kindle_thumb.jpg</thumbSize>
35
- </images>
36
- </product>
37
- <product>
38
- <name>Kindle Touch - Simple-to-use touchscreen with built-in WIFI.</name>
39
- <images>
40
- <thumbSize width="120" height="90">http://amazon.com/kindle_touch_thumb.jpg</thumbSize>
41
- </images>
42
- </product>
43
- </products>
44
- </webstore>
45
-
46
- You instantiate the parser by passing path to XML file and object-identyfing tag name as its arguments.
47
-
48
- The following will parse the XML, find product definitions (inside `<product>` and `</product>` tags), build `OpenStruct`s and yield them inside the block.
49
- Tag attributes become object attributes and attributes' name are underscored.
50
-
51
- Saxy.parse("filename.xml", "product").each do |product|
52
- puts product.name
53
- puts product.images.thumb_size.contents
54
- puts "#{product.images.thumb_size.width}x#{product.images.thumb_size.height}"
55
- end
56
-
57
- # =>
58
- Kindle - The world's best-selling e-reader.
59
- http://amazon.com/kindle_thumb.jpg
60
- 80x60
61
- Kindle Touch - Simple-to-use touchscreen with built-in WIFI.
62
- http://amazon.com/kindle_touch_thumb.jpg
63
- 120x90
48
+ You instantiate the parser by passing path to XML file or an IO-like object, object-identifying tag name and options hash (optionally) as its arguments.
49
+
50
+ ```ruby
51
+ parser = Saxy.parse(path_or_io, object_tag, options = {})
52
+ ```
53
+
54
+ Then iterate over it using `each` (or any of convenient methods provided by `Enumerable` mix-in).
55
+
56
+ ```ruby
57
+ parser.each do |object|
58
+ ...
59
+ end
60
+ ```
61
+
62
+ ### Options
63
+
64
+ * `encoding` - Forces the parser to work in given encoding
65
+ * `recovery` - Should this parser recover from structural errors? It will not stop processing file on structural errors if set to `true`.
66
+ * `replace_entities` - Should this parser replace entities? `&amp;` will get converted to `&` if set to `true`.
67
+ * `error_handler` - If set to a callable, parser will call it with any error it encounters instead of raising exceptions.
68
+
69
+ Combination of `error_handler` and `recovery` options allows for continued processing when encountering recoverable errors (e.g. unescaped [predefined entities](https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references#Predefined_entities_in_XML)).
70
+
71
+ ```ruby
72
+ error_handler = proc { |e| $stderr.puts "#{e.message} at line #{e.context.line}, column #{e.context.column}." }
73
+ Saxy.parse(path_or_io, object_tag, error_handler: error_handler, recovery: true) { ... }
74
+ ```
75
+
76
+ ## Example
77
+
78
+ Assume the XML file (an imaginary product feed):
79
+
80
+ ````xml
81
+ <?xml version='1.0' encoding='UTF-8'?>
82
+ <webstore>
83
+ <name>Amazon</name>
84
+ <products>
85
+ <product>
86
+ <name>Kindle - The world's best-selling e-reader.</name>
87
+ <images>
88
+ <thumbSize width="80" height="60">http://amazon.com/kindle_thumb.jpg</thumbSize>
89
+ </images>
90
+ </product>
91
+ <product>
92
+ <name>Kindle Touch - Simple-to-use touchscreen with built-in WIFI.</name>
93
+ <images>
94
+ <thumbSize width="120" height="90">http://amazon.com/kindle_touch_thumb.jpg</thumbSize>
95
+ </images>
96
+ </product>
97
+ </products>
98
+ </webstore>
99
+ ````
100
+
101
+ The following will parse the XML, find product definitions (inside `<product>` and `</product>` tags), build `Hash`es and yield them inside the block.
102
+
103
+ Usage with a file path:
104
+
105
+ ````ruby
106
+ Saxy.parse("filename.xml", "product").each do |product|
107
+ puts product["name"]
108
+ puts product["images"]["thumb_size"]["contents"]
109
+ puts "#{product["images"]["thumb_size"]["width"]}x#{product["images"]["thumb_size"]["height"]}"
110
+ end
111
+
112
+ # =>
113
+ "Kindle - The world's best-selling e-reader."
114
+ "http://amazon.com/kindle_thumb.jpg"
115
+ "80x60"
116
+ "Kindle Touch - Simple-to-use touchscreen with built-in WIFI."
117
+ "http://amazon.com/kindle_touch_thumb.jpg"
118
+ "120x90"
119
+ ````
120
+
121
+ Usage with an IO-like object `ARGF` or `$stdin`:
122
+
123
+ ````ruby
124
+ # > cat filename.xml | ruby this_script.rb
125
+ Saxy.parse(ARGF, "product").each do |product|
126
+ puts product["name"]
127
+ end
128
+
129
+ # =>
130
+ "Kindle - The world's best-selling e-reader."
131
+ ````
64
132
 
65
133
  Saxy supports Enumerable, so you can use its goodies to your comfort without building intermediate arrays:
66
134
 
67
- Saxy.parse("filename.xml", "product").map do |object|
68
- # map OpenStructs to ActiveRecord instances, etc.
69
- end
135
+ ````ruby
136
+ Saxy.parse("filename.xml", "product").map do |object|
137
+ # map yielded Hash to ActiveRecord instances, etc.
138
+ end
139
+ ````
70
140
 
71
141
  You can also grab an Enumerator for external use (e.g. lazy evaluation, etc.):
72
142
 
73
- enumerator = Saxy.parse("filename.xml", "product").each
74
- lazy = Saxy.parse("filename.xml", "product").lazy # Ruby 2.0
143
+ ````ruby
144
+ enumerator = Saxy.parse("filename.xml", "product").each
145
+ lazy = Saxy.parse("filename.xml", "product").lazy # Ruby 2.0
146
+ ````
75
147
 
76
148
  Multiple definitions of child objects are grouped in arrays:
77
149
 
78
- webstore = Saxy.parse("filename.xml", "webstore").first
79
- webstore.products.product.size # => 2
150
+ ````ruby
151
+ webstore = Saxy.parse("filename.xml", "webstore").first
152
+ webstore["products"]["product"].size # => 2
153
+ ````
154
+
155
+ ## Debugging
156
+
157
+ Invalid XML files happen a lot and error messages are not always extremely helpful. In case of a parsing error, some additional information can be retrieved from parser's context.
158
+
159
+ ```ruby
160
+ begin
161
+ Saxy.parse(...) { ... }
162
+ rescue e => Saxy::ParsingError
163
+ puts "#{e.message} at #{e.context.line} line and #{e.context.column}"
164
+ end
165
+ ```
80
166
 
81
167
  ## Contributing
82
168
 
@@ -85,3 +171,11 @@ Multiple definitions of child objects are grouped in arrays:
85
171
  3. Commit your changes (`git commit -am 'Added some feature'`)
86
172
  4. Push to the branch (`git push origin my-new-feature`)
87
173
  5. Create new Pull Request
174
+
175
+ ## License
176
+
177
+ See `LICENSE.txt` file.
178
+
179
+ ## Author
180
+
181
+ Michał Szajbe, [@szajbus](https://twitter.com/szajbus), [szajbe.pl](http://szajbe.pl)
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "nokogiri", "~> 1.6.0"
6
+
7
+ gemspec :path => "../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "nokogiri"
6
+
7
+ gemspec :path => "../"
data/lib/saxy/element.rb CHANGED
@@ -1,8 +1,6 @@
1
- require 'active_support/core_ext/string/inflections'
2
-
3
1
  module Saxy
4
2
  class Element
5
- attr_reader :attributes, :value
3
+ attr_reader :attributes
6
4
 
7
5
  def initialize
8
6
  @attributes = {}
@@ -11,33 +9,46 @@ module Saxy
11
9
 
12
10
  def set_attribute(name, value)
13
11
  name = attribute_name(name)
12
+
14
13
  attributes[name] ||= []
15
14
  attributes[name] << value
16
15
  end
17
16
 
18
17
  def append_value(string)
19
- unless (string = string.strip).empty?
18
+ if @value || !string.strip.empty?
20
19
  @value ||= ""
21
20
  @value << string
22
21
  end
23
22
  end
24
23
 
25
- def as_object
26
- if attributes.any?
27
- object = OpenStruct.new
28
- attributes.each do |name, value|
29
- value = value.first if value.size == 1
30
- object.send("#{name}=", value)
31
- end
32
- object.contents = value
33
- object
34
- else
35
- value
24
+ def value
25
+ @value && @value.strip
26
+ end
27
+
28
+ def to_h
29
+ return value unless attributes.any?
30
+
31
+ data = attributes.reduce({}) do |memo, (name, value)|
32
+ memo[name] = value.size == 1 ? value.first : value
33
+ memo
36
34
  end
35
+
36
+ data["contents"] = value
37
+ data
37
38
  end
38
39
 
39
40
  def attribute_name(name)
40
- name.underscore
41
+ underscore(name)
42
+ end
43
+
44
+ private
45
+
46
+ def underscore(word)
47
+ word
48
+ .gsub(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
49
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
50
+ .tr("-", "_")
51
+ .downcase
41
52
  end
42
53
  end
43
54
  end
data/lib/saxy/parser.rb CHANGED
@@ -16,8 +16,14 @@ module Saxy
16
16
  # Will yield objects inside the callback after they're built
17
17
  attr_reader :callback
18
18
 
19
- def initialize(xml_file, object_tag)
20
- @xml_file, @object_tag = xml_file, object_tag
19
+ # Parser context
20
+ attr_reader :context
21
+
22
+ # Parser options
23
+ attr_reader :options
24
+
25
+ def initialize(object, object_tag, options={})
26
+ @object, @object_tag, @options = object, object_tag, options
21
27
  @tags, @elements = [], []
22
28
  end
23
29
 
@@ -35,8 +41,9 @@ module Saxy
35
41
 
36
42
  def end_element(tag)
37
43
  tags.pop
44
+
38
45
  if element = elements.pop
39
- object = element.as_object
46
+ object = element.to_h
40
47
 
41
48
  if current_element
42
49
  current_element.set_attribute(tag, object)
@@ -55,7 +62,13 @@ module Saxy
55
62
  end
56
63
 
57
64
  def error(message)
58
- raise ParsingError.new(message)
65
+ error = ParsingError.new(message, context)
66
+
67
+ if options[:error_handler].respond_to?(:call)
68
+ options[:error_handler].call(error)
69
+ else
70
+ raise error
71
+ end
59
72
  end
60
73
 
61
74
  def current_element
@@ -63,19 +76,28 @@ module Saxy
63
76
  end
64
77
 
65
78
  def each(&blk)
66
- if blk
67
- @callback = blk
79
+ return to_enum unless blk
68
80
 
69
- parser = Nokogiri::XML::SAX::Parser.new(self)
81
+ @callback = blk
70
82
 
71
- if @xml_file.is_a?(IO)
72
- parser.parse_io(@xml_file)
73
- else
74
- parser.parse_file(@xml_file)
75
- end
83
+ args = [self, options[:encoding]].compact
84
+ parser = Nokogiri::XML::SAX::Parser.new(*args)
85
+
86
+ if @object.respond_to?(:read) && @object.respond_to?(:close)
87
+ parser.parse_io(@object, &context_blk)
76
88
  else
77
- to_enum
89
+ parser.parse_file(@object, &context_blk)
78
90
  end
79
91
  end
92
+
93
+ def context_blk
94
+ proc { |context|
95
+ [:recovery, :replace_entities].each do |key|
96
+ context.send("#{key}=", options[key]) if options.has_key?(key)
97
+ end
98
+
99
+ @context = context
100
+ }
101
+ end
80
102
  end
81
103
  end
@@ -1,4 +1,10 @@
1
1
  module Saxy
2
- class ParsingError < ::Exception
2
+ class ParsingError < ::StandardError
3
+ attr_reader :context
4
+
5
+ def initialize(message, context)
6
+ @context = context
7
+ super(message)
8
+ end
3
9
  end
4
- end
10
+ end
data/lib/saxy/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Saxy
2
- VERSION = "0.2.3"
2
+ VERSION = "0.7.1"
3
3
  end
data/lib/saxy.rb CHANGED
@@ -2,8 +2,8 @@ require 'saxy/version'
2
2
 
3
3
  module Saxy
4
4
  class << self
5
- def parse(xml_file, object_tag, &blk)
6
- parser = Parser.new(xml_file, object_tag)
5
+ def parse(object, object_tag, options={}, &blk)
6
+ parser = Parser.new(object, object_tag, options)
7
7
 
8
8
  if blk
9
9
  parser.each(blk)
@@ -12,13 +12,9 @@ module Saxy
12
12
  end
13
13
  end
14
14
 
15
- def ruby_18?
16
- @ruby_18 ||= RUBY_VERSION =~ /^1\.8/
17
- end
18
15
  end
19
16
  end
20
17
 
21
18
  require 'saxy/element'
22
- require 'saxy/ostruct'
23
19
  require 'saxy/parser'
24
20
  require 'saxy/parsing_error'
data/saxy.gemspec CHANGED
@@ -4,9 +4,9 @@ require File.expand_path('../lib/saxy/version', __FILE__)
4
4
  Gem::Specification.new do |gem|
5
5
  gem.authors = ["Michał Szajbe"]
6
6
  gem.email = ["michal.szajbe@gmail.com"]
7
- gem.description = %q{Saxy finds object definitions in XML files and translates them into Ruby objects. It uses SAX parser under the hood, which means that it doesn't load the whole XML file into memory. It goes once though it and yields objects along the way.}
7
+ gem.description = %q{Saxy finds object definitions in XML files and translates them into Ruby objects. It uses SAX parser under the hood, which means that it doesn't load the whole XML file into memory. It goes once through it and yields objects along the way.}
8
8
  gem.summary = %q{Memory-efficient XML parser. Finds object definitions and translates them into Ruby objects.}
9
- gem.homepage = "http://github.com/monterail/saxy"
9
+ gem.homepage = "http://github.com/humante/saxy"
10
10
 
11
11
  gem.files = `git ls-files`.split($\)
12
12
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
@@ -15,7 +15,10 @@ Gem::Specification.new do |gem|
15
15
  gem.require_paths = ["lib"]
16
16
  gem.version = Saxy::VERSION
17
17
 
18
- gem.add_dependency "activesupport"
18
+ gem.required_ruby_version = ">= 1.9.3"
19
+
19
20
  gem.add_dependency "nokogiri"
21
+
20
22
  gem.add_development_dependency "rspec"
23
+ gem.add_development_dependency "appraisal"
21
24
  end
@@ -0,0 +1,9 @@
1
+ <?xml version='1.0' encoding='UTF-8'?>
2
+ <webstore>
3
+ <products>
4
+ <product>
5
+ <uid>FFCF177</uid>
6
+ <name>Unescaped & ampersand</name>
7
+ </product>
8
+ </products>
9
+ </webstore>
@@ -5,45 +5,47 @@ describe Saxy::Element do
5
5
 
6
6
  it "should not append empty string as value" do
7
7
  element.append_value("")
8
- element.value.should be_nil
8
+ expect(element.value).to be_nil
9
9
  end
10
10
 
11
- it "should append stripped value" do
12
- element.append_value(" foo ")
13
- element.append_value(" bar ")
14
- element.value.should == "foobar"
11
+ it "should strip leading and trailing whitespace" do
12
+ element.append_value(" one two ")
13
+ element.append_value("&")
14
+ element.append_value(" three\nfour")
15
+ element.append_value("\t\t")
16
+ expect(element.value).to eq("one two & three\nfour")
15
17
  end
16
18
 
17
19
  it "should dump as string when no attributes are set" do
18
- element.stub(:value).and_return("foo")
19
- element.as_object.should == "foo"
20
+ expect(element).to receive(:value).and_return("foo")
21
+ expect(element.to_h).to eq("foo")
20
22
  end
21
23
 
22
24
  it "should dump as object when attributes are set" do
23
- element.stub(:attributes).and_return("foo" => 1, "bar" => 2)
24
- object = element.as_object
25
+ expect(element).to receive(:attributes).at_least(:once).and_return("foo" => 1, "bar" => 2)
26
+ object = element.to_h
25
27
 
26
- object.foo.should == 1
27
- object.bar.should == 2
28
+ expect(object["foo"]).to eq(1)
29
+ expect(object["bar"]).to eq(2)
28
30
  end
29
31
 
30
32
  it "should dump as object with value when attributes and contents are set" do
31
33
  element.set_attribute("foo", "bar")
32
34
  element.append_value("value")
33
- object = element.as_object
35
+ object = element.to_h
34
36
 
35
- object.foo.should == "bar"
36
- object.contents.should == "value"
37
+ expect(object["foo"]).to eq("bar")
38
+ expect(object["contents"]).to eq("value")
37
39
  end
38
40
 
39
41
  it "should add attributes under underscored names" do
40
42
  element.set_attribute("FooBar", "baz")
41
- element.as_object.foo_bar.should == "baz"
43
+ expect(element.to_h["foo_bar"]).to eq("baz")
42
44
  end
43
45
 
44
46
  it "should create array if adding multiple attributtes with the same name" do
45
47
  element.set_attribute("foo", "bar")
46
48
  element.set_attribute("foo", "baz")
47
- element.as_object.foo.should == ["bar", "baz"]
49
+ expect(element.to_h["foo"]).to eq(["bar", "baz"])
48
50
  end
49
51
  end
@@ -4,43 +4,62 @@ describe Saxy::Parser do
4
4
  include FixturesHelper
5
5
 
6
6
  let(:parser) { Saxy::Parser.new(fixture_file("webstore.xml"), "product") }
7
+ let(:file_io) { File.new(fixture_file("webstore.xml")) }
8
+ let(:io_like) { IOLike.new(file_io) }
7
9
 
8
- it "should accept string filename as xml_file" do
10
+ it "should accept string filename for parsing" do
9
11
  xml_file = fixture_file("webstore.xml")
10
12
  parser = Saxy::Parser.new(xml_file, "product")
11
- parser.each.to_a.size.should == 2
13
+ expect(parser.each.to_a.size).to eq(2)
12
14
  end
13
15
 
14
- it "should accept IO as xml_file" do
15
- xml_file = File.new(fixture_file("webstore.xml"))
16
- parser = Saxy::Parser.new(xml_file, "product")
17
- parser.each.to_a.size.should == 2
16
+ it "should accept IO for parsing" do
17
+ parser = Saxy::Parser.new(file_io, "product")
18
+ expect(parser.each.to_a.size).to eq(2)
19
+ end
20
+
21
+ it "should accept optional force-encoding" do
22
+ parser = Saxy::Parser.new(file_io, "product", encoding: "UTF-8")
23
+ expect(Nokogiri::XML::SAX::Parser).to receive(:new).with(parser, "UTF-8").and_call_original
24
+ expect(parser.each.to_a.size).to eq(2)
25
+ end
26
+
27
+ it "should pass options to parser context" do
28
+ parser = Saxy::Parser.new(file_io, "product", recovery: true, replace_entities: true)
29
+ parser.each.to_a
30
+ expect(parser.context.recovery).to be_truthy
31
+ expect(parser.context.replace_entities).to be_truthy
32
+ end
33
+
34
+ it "should accept an IO-like for parsing" do
35
+ parser = Saxy::Parser.new(io_like, "product")
36
+ expect(parser.each.to_a.size).to eq(2)
18
37
  end
19
38
 
20
39
  it "should have empty tag stack" do
21
- parser.tags.should == %w( )
40
+ expect(parser.tags).to eq(%w( ))
22
41
  end
23
42
 
24
43
  it "should push/pop tag names on/from tag stack when going down/up the XML tree" do
25
- parser.tags.should == %w( )
44
+ expect(parser.tags).to eq(%w( ))
26
45
 
27
46
  parser.start_element('webstore')
28
- parser.tags.should == %w( webstore )
47
+ expect(parser.tags).to eq(%w( webstore ))
29
48
 
30
49
  parser.start_element('products')
31
- parser.tags.should == %w( webstore products )
50
+ expect(parser.tags).to eq(%w( webstore products ))
32
51
 
33
52
  parser.start_element('product')
34
- parser.tags.should == %w( webstore products product )
53
+ expect(parser.tags).to eq(%w( webstore products product ))
35
54
 
36
55
  parser.end_element('product')
37
- parser.tags.should == %w( webstore products )
56
+ expect(parser.tags).to eq(%w( webstore products ))
38
57
 
39
58
  parser.end_element('products')
40
- parser.tags.should == %w( webstore )
59
+ expect(parser.tags).to eq(%w( webstore ))
41
60
 
42
61
  parser.end_element('webstore')
43
- parser.tags.should == %w( )
62
+ expect(parser.tags).to eq(%w( ))
44
63
  end
45
64
 
46
65
  context "when detecting object tag opening" do
@@ -49,7 +68,7 @@ describe Saxy::Parser do
49
68
  end
50
69
 
51
70
  it "should add new element to stack" do
52
- parser.elements.size.should == 1
71
+ expect(parser.elements.size).to eq(1)
53
72
  end
54
73
  end
55
74
 
@@ -59,14 +78,14 @@ describe Saxy::Parser do
59
78
  end
60
79
 
61
80
  it "should not add new element to stack" do
62
- parser.elements.should be_empty
81
+ expect(parser.elements).to be_empty
63
82
  end
64
83
  end
65
84
 
66
85
  context "with non-empty element stack" do
67
86
  before do
68
87
  parser.start_element("product")
69
- parser.elements.should_not be_empty
88
+ expect(parser.elements).to_not be_empty
70
89
  end
71
90
 
72
91
  context "when detecting object tag opening" do
@@ -75,7 +94,7 @@ describe Saxy::Parser do
75
94
  end
76
95
 
77
96
  it "should add new element to stack" do
78
- parser.elements.size.should == 2
97
+ expect(parser.elements.size).to eq(2)
79
98
  end
80
99
  end
81
100
 
@@ -85,7 +104,7 @@ describe Saxy::Parser do
85
104
  end
86
105
 
87
106
  it "should not add new element to stack" do
88
- parser.elements.size.should == 2
107
+ expect(parser.elements.size).to eq(2)
89
108
  end
90
109
  end
91
110
 
@@ -95,36 +114,36 @@ describe Saxy::Parser do
95
114
  end
96
115
 
97
116
  it "should pop element from stack" do
98
- parser.elements.should be_empty
117
+ expect(parser.elements).to be_empty
99
118
  end
100
119
  end
101
120
 
102
121
  context "with callback defined" do
103
122
  before do
104
123
  @callback = lambda { |object| object }
105
- parser.stub(:callback).and_return(@callback)
124
+ allow(parser).to receive(:callback).and_return(@callback)
106
125
  end
107
126
 
108
127
  it "should yield the object inside the callback after detecting object tag closing" do
109
- @callback.should_receive(:call).with(parser.current_element.as_object)
128
+ expect(@callback).to receive(:call).with(parser.current_element.to_h)
110
129
  parser.end_element("product")
111
130
  end
112
131
 
113
132
  it "should not yield the object inside the callback after detecting other tag closing" do
114
133
  parser.start_element("other")
115
- @callback.should_not_receive(:call)
134
+ expect(@callback).to_not receive(:call)
116
135
  parser.end_element("other")
117
136
  end
118
137
  end
119
138
 
120
139
  it "should append cdata block's contents to top element's value when detecting cdata block" do
121
- parser.current_element.should_receive(:append_value).with("foo")
140
+ expect(parser.current_element).to receive(:append_value).with("foo")
122
141
  parser.cdata_block("foo")
123
142
  end
124
143
 
125
144
  it "should append characters to top element's value when detecting characters block" do
126
- parser.current_element.should_receive(:append_value).with("foo")
127
- parser.current_element.should_receive(:append_value).with("bar")
145
+ expect(parser.current_element).to receive(:append_value).with("foo")
146
+ expect(parser.current_element).to receive(:append_value).with("bar")
128
147
  parser.characters("foo")
129
148
  parser.characters("bar")
130
149
  end
@@ -132,7 +151,7 @@ describe Saxy::Parser do
132
151
  it "should set element's attribute after processing tags" do
133
152
  element = parser.current_element
134
153
 
135
- element.should_receive(:set_attribute).with("foo", "bar")
154
+ expect(element).to receive(:set_attribute).with("foo", "bar")
136
155
 
137
156
  parser.start_element("foo")
138
157
  parser.characters("bar")
@@ -141,19 +160,33 @@ describe Saxy::Parser do
141
160
 
142
161
  it "should set element's attributes when opening tag with attributes" do
143
162
  parser.start_element("foo", [["bar", "baz"]])
144
- parser.current_element.as_object.bar.should == "baz"
163
+ expect(parser.current_element.to_h["bar"]).to eq("baz")
145
164
  end
146
165
  end
147
166
 
148
- it "should raise Saxy::ParsingError on error" do
149
- lambda { parser.error("Error message.") }.should raise_error(Saxy::ParsingError, "Error message.")
167
+ context "when error handler is not set" do
168
+ let(:parser) { Saxy::Parser.new(fixture_file("invalid.xml"), "product") }
169
+
170
+ it "should raise Saxy::ParsingError on error" do
171
+ expect { parser.each.to_a }.to raise_error { |error|
172
+ expect(error).to be_a(Saxy::ParsingError)
173
+ expect(error.message).to match(/xmlParseEntityRef: no name/)
174
+ expect(error.context).to be_a(Nokogiri::XML::SAX::ParserContext)
175
+ }
176
+ end
150
177
  end
151
178
 
152
- it "should return Enumerator when calling #each without a block", :unless => RUBY_1_8 do
153
- parser.each.should be_instance_of Enumerator
179
+ context "when error handler is set" do
180
+ let(:handler) { proc { |error| error } }
181
+ let(:parser) { Saxy::Parser.new(fixture_file("invalid.xml"), "product", error_handler: handler) }
182
+
183
+ it "should call error handler passing Saxy::ParsingError instance" do
184
+ expect(handler).to receive(:call).with(Saxy::ParsingError)
185
+ parser.each.to_a
186
+ end
154
187
  end
155
188
 
156
- it "should return Enumerator when calling #each without a block", :if => RUBY_1_8 do
157
- parser.each.should be_instance_of Enumerable::Enumerator
189
+ it "should return Enumerator when calling #each without a block" do
190
+ expect(parser.each).to be_an(Enumerator)
158
191
  end
159
192
  end
data/spec/saxy_spec.rb CHANGED
@@ -9,33 +9,34 @@ describe Saxy do
9
9
  arr
10
10
  end
11
11
 
12
- products[0].uid.should == "FFCF177"
13
- products[0].name.should == "Kindle"
14
- products[0].description.should == "The world's best-selling e-reader."
15
- products[0].price.should == "$109"
16
- products[0].images.thumb.should == "http://amazon.com/kindle_thumb.jpg"
17
- products[0].images.large.should == "http://amazon.com/kindle.jpg"
18
-
19
- products[1].uid.should == "YD26NT"
20
- products[1].name.should == "Kindle Touch"
21
- products[1].description.should == "Simple-to-use touchscreen with built-in WIFI."
22
- products[1].price.should == "$79"
23
- products[1].images.thumb.should == "http://amazon.com/kindle_touch_thumb.jpg"
24
- products[1].images.large.should == "http://amazon.com/kindle_touch.jpg"
12
+ expect(products[0]["uid"]).to eq("FFCF177")
13
+ expect(products[0]["name"]).to eq("Kindle")
14
+ expect(products[0]["description"]).to eq("The world's best-selling e-reader.")
15
+ expect(products[0]["price"]).to eq("$109")
16
+ expect(products[0]["images"]["thumb"]).to eq("http://amazon.com/kindle_thumb.jpg")
17
+ expect(products[0]["images"]["large"]).to eq("http://amazon.com/kindle.jpg")
18
+
19
+ expect(products[1]["uid"]).to eq("YD26NT")
20
+ expect(products[1]["name"]).to eq("Kindle Touch")
21
+ expect(products[1]["description"]).to eq("Simple-to-use touchscreen with built-in WIFI.")
22
+ expect(products[1]["price"]).to eq("$79")
23
+ expect(products[1]["images"]["thumb"]).to eq("http://amazon.com/kindle_touch_thumb.jpg")
24
+ expect(products[1]["images"]["large"]).to eq("http://amazon.com/kindle_touch.jpg")
25
25
  end
26
26
 
27
27
  it "should group multiple definitions of child objects into arrays" do
28
28
  webstore = Saxy.parse(fixture_file("webstore.xml"), "webstore").first
29
29
 
30
- webstore.products.product.should be_instance_of Array
31
- webstore.products.product.size.should == 2
30
+ expect(webstore["products"]["product"]).to be_an(Array)
31
+ expect(webstore["products"]["product"].size).to eq(2)
32
32
  end
33
33
 
34
- it "should return Enumerator when calling #parse without a block", :unless => RUBY_1_8 do
35
- Saxy.parse(fixture_file("webstore.xml"), "product").each.should be_instance_of Enumerator
34
+ it "should return Enumerator when calling #parse without a block" do
35
+ expect(Saxy.parse(fixture_file("webstore.xml"), "product").each).to be_an(Enumerator)
36
36
  end
37
37
 
38
- it "should return Enumerator when calling #parse without a block", :if => RUBY_1_8 do
39
- Saxy.parse(fixture_file("webstore.xml"), "product").each.should be_instance_of Enumerable::Enumerator
38
+ it "should pass options to Parser's initializer" do
39
+ expect(Saxy::Parser).to receive(:new).with("filename", "object_tag", { foo: 'bar' }).and_call_original
40
+ Saxy.parse("filename", "object_tag", { foo: 'bar' })
40
41
  end
41
- end
42
+ end
data/spec/spec_helper.rb CHANGED
@@ -3,5 +3,4 @@ require 'bundler/setup'
3
3
  require 'saxy'
4
4
 
5
5
  require 'fixtures_helper'
6
-
7
- RUBY_1_8 = (RUBY_VERSION =~ /^1\.8/)
6
+ require 'support/io_like'
@@ -0,0 +1,9 @@
1
+ class IOLike
2
+ extend Forwardable
3
+
4
+ def_delegators :@io, :read, :close
5
+
6
+ def initialize(io)
7
+ @io = io
8
+ end
9
+ end
metadata CHANGED
@@ -1,60 +1,60 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: saxy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michał Szajbe
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-10-25 00:00:00.000000000 Z
11
+ date: 2018-04-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: activesupport
14
+ name: nokogiri
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - '>='
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - '>='
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: nokogiri
28
+ name: rspec
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - '>='
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '0'
34
- type: :runtime
34
+ type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - '>='
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: rspec
42
+ name: appraisal
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - '>='
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - '>='
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  description: Saxy finds object definitions in XML files and translates them into Ruby
56
56
  objects. It uses SAX parser under the hood, which means that it doesn't load the
57
- whole XML file into memory. It goes once though it and yields objects along the
57
+ whole XML file into memory. It goes once through it and yields objects along the
58
58
  way.
59
59
  email:
60
60
  - michal.szajbe@gmail.com
@@ -62,29 +62,32 @@ executables: []
62
62
  extensions: []
63
63
  extra_rdoc_files: []
64
64
  files:
65
- - .gitignore
66
- - .rspec
67
- - .travis.yml
65
+ - ".gitignore"
66
+ - ".rspec"
67
+ - ".travis.yml"
68
+ - Appraisals
69
+ - CHANGELOG.md
68
70
  - Gemfile
69
- - LICENSE
71
+ - LICENSE.txt
70
72
  - README.md
71
73
  - Rakefile
72
- - gemfiles/gemfile-1.8
74
+ - gemfiles/nokogiri_1.6.gemfile
75
+ - gemfiles/nokogiri_latest.gemfile
73
76
  - lib/saxy.rb
74
77
  - lib/saxy/element.rb
75
- - lib/saxy/ostruct.rb
76
78
  - lib/saxy/parser.rb
77
79
  - lib/saxy/parsing_error.rb
78
80
  - lib/saxy/version.rb
79
81
  - saxy.gemspec
82
+ - spec/fixtures/invalid.xml
80
83
  - spec/fixtures/webstore.xml
81
84
  - spec/fixtures_helper.rb
82
85
  - spec/saxy/element_spec.rb
83
- - spec/saxy/ostruct_spec.rb
84
86
  - spec/saxy/parser_spec.rb
85
87
  - spec/saxy_spec.rb
86
88
  - spec/spec_helper.rb
87
- homepage: http://github.com/monterail/saxy
89
+ - spec/support/io_like.rb
90
+ homepage: http://github.com/humante/saxy
88
91
  licenses: []
89
92
  metadata: {}
90
93
  post_install_message:
@@ -93,27 +96,27 @@ require_paths:
93
96
  - lib
94
97
  required_ruby_version: !ruby/object:Gem::Requirement
95
98
  requirements:
96
- - - '>='
99
+ - - ">="
97
100
  - !ruby/object:Gem::Version
98
- version: '0'
101
+ version: 1.9.3
99
102
  required_rubygems_version: !ruby/object:Gem::Requirement
100
103
  requirements:
101
- - - '>='
104
+ - - ">="
102
105
  - !ruby/object:Gem::Version
103
106
  version: '0'
104
107
  requirements: []
105
108
  rubyforge_project:
106
- rubygems_version: 2.0.0
109
+ rubygems_version: 2.5.1
107
110
  signing_key:
108
111
  specification_version: 4
109
112
  summary: Memory-efficient XML parser. Finds object definitions and translates them
110
113
  into Ruby objects.
111
114
  test_files:
115
+ - spec/fixtures/invalid.xml
112
116
  - spec/fixtures/webstore.xml
113
117
  - spec/fixtures_helper.rb
114
118
  - spec/saxy/element_spec.rb
115
- - spec/saxy/ostruct_spec.rb
116
119
  - spec/saxy/parser_spec.rb
117
120
  - spec/saxy_spec.rb
118
121
  - spec/spec_helper.rb
119
- has_rdoc:
122
+ - spec/support/io_like.rb
data/LICENSE DELETED
@@ -1,22 +0,0 @@
1
- Copyright (c) 2012 Michał Szajbe
2
-
3
- MIT License
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining
6
- a copy of this software and associated documentation files (the
7
- "Software"), to deal in the Software without restriction, including
8
- without limitation the rights to use, copy, modify, merge, publish,
9
- distribute, sublicense, and/or sell copies of the Software, and to
10
- permit persons to whom the Software is furnished to do so, subject to
11
- the following conditions:
12
-
13
- The above copyright notice and this permission notice shall be
14
- included in all copies or substantial portions of the Software.
15
-
16
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/gemfiles/gemfile-1.8 DELETED
@@ -1,6 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- gem "activesupport", "< 4.0.0"
4
- gem "saxy", :path => "../"
5
- gemspec :path => "../"
6
-
data/lib/saxy/ostruct.rb DELETED
@@ -1,11 +0,0 @@
1
- require 'ostruct'
2
-
3
- module Saxy
4
- class OpenStruct < ::OpenStruct
5
- if Saxy.ruby_18?
6
- define_method :id do
7
- @table[:id]
8
- end
9
- end
10
- end
11
- end
@@ -1,14 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Saxy::OpenStruct do
4
- let(:object) { Saxy::OpenStruct.new }
5
-
6
- if Saxy.ruby_18?
7
- context "in Ruby 1.8" do
8
- it "should correctly set id attribute" do
9
- object.id = 1
10
- object.id.should == 1
11
- end
12
- end
13
- end
14
- end